001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.hbase.util;
020
021import java.util.List;
022import java.util.Map;
023import java.util.Map.Entry;
024
025import org.apache.yetus.audience.InterfaceAudience;
026
027import org.apache.hbase.thirdparty.com.google.common.escape.Escaper;
028import org.apache.hbase.thirdparty.com.google.common.escape.Escapers;
029
030/**
031 * Utility class for converting objects to JRuby.
032 *
033 * It handles null, Boolean, Number, String, byte[], List<Object>, Map<String, Object> structures.
034 *
035 * <p>
036 * E.g.
037 * <pre>
038 * Map<String, Object> map = new LinkedHashMap<>();
039 * map.put("null", null);
040 * map.put("boolean", true);
041 * map.put("number", 1);
042 * map.put("string", "str");
043 * map.put("binary", new byte[] { 1, 2, 3 });
044 * map.put("list", Lists.newArrayList(1, "2", true));
045 * </pre>
046 * </p>
047 *
048 * <p>
049 * Calling {@link #print(Object)} method will result:
050 * <pre>
051 * { null => '', boolean => 'true', number => '1', string => 'str', binary => '010203', list => [ '1', '2', 'true' ] }
052 * </pre>
053 * </p>
054 */
055@InterfaceAudience.Private
056public final class JRubyFormat {
057  private static final Escaper escaper;
058
059  static {
060    escaper = Escapers.builder()
061      .addEscape('\\', "\\\\")
062      .addEscape('\'', "\\'")
063      .addEscape('\n', "\\n")
064      .addEscape('\r', "\\r")
065      .addEscape('\t', "\\t")
066      .addEscape('\f', "\\f")
067      .build();
068  }
069
070  private JRubyFormat() {
071  }
072
073  private static String escape(Object object) {
074    if (object == null) {
075      return "";
076    } else {
077      return escaper.escape(object.toString());
078    }
079  }
080
081  @SuppressWarnings({ "unchecked" })
082  private static void appendJRuby(StringBuilder builder, Object object) {
083    if (object == null) {
084      builder.append("''");
085    } else if (object instanceof List) {
086      builder.append("[");
087
088      boolean first = true;
089
090      for (Object element: (List<Object>)object) {
091        if (first) {
092          first = false;
093          builder.append(" ");
094        } else {
095          builder.append(", ");
096        }
097
098        appendJRuby(builder, element);
099      }
100
101      if (!first) {
102        builder.append(" ");
103      }
104
105      builder.append("]");
106    } else if (object instanceof Map) {
107      builder.append("{");
108
109      boolean first = true;
110
111      for (Entry<String, Object> entry: ((Map<String, Object>)object).entrySet()) {
112        if (first) {
113          first = false;
114          builder.append(" ");
115        } else {
116          builder.append(", ");
117        }
118
119        String key = entry.getKey();
120        String escapedKey = escape(key);
121
122        if (key.equals(escapedKey)) {
123          builder.append(key);
124        } else {
125          builder.append("'").append(escapedKey).append("'");
126        }
127
128        builder.append(" => ");
129        appendJRuby(builder, entry.getValue());
130      }
131
132      if (!first) {
133        builder.append(" ");
134      }
135
136      builder.append("}");
137    } else if (object instanceof byte[]) {
138      String byteString = Bytes.toHex((byte[])object);
139      builder.append("'").append(escape(byteString)).append("'");
140    } else {
141      builder.append("'").append(escape(object)).append("'");
142    }
143  }
144
145  public static String print(Object object) {
146    StringBuilder builder = new StringBuilder();
147
148    appendJRuby(builder, object);
149
150    return builder.toString();
151  }
152}