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 */
018package org.apache.hadoop.hbase.util;
019
020import java.util.List;
021import java.util.Map;
022import java.util.Map.Entry;
023import org.apache.yetus.audience.InterfaceAudience;
024
025import org.apache.hbase.thirdparty.com.google.common.escape.Escaper;
026import org.apache.hbase.thirdparty.com.google.common.escape.Escapers;
027
028/**
029 * Utility class for converting objects to JRuby. It handles null, Boolean, Number, String, byte[],
030 * List<Object>, Map<String, Object> structures.
031 * <p>
032 * E.g.
033 *
034 * <pre>
035 * Map&lt;String, Object&gt; map = new LinkedHashMap&lt;&gt;();
036 * map.put("null", null);
037 * map.put("boolean", true);
038 * map.put("number", 1);
039 * map.put("string", "str");
040 * map.put("binary", new byte[] { 1, 2, 3 });
041 * map.put("list", Lists.newArrayList(1, "2", true));
042 * </pre>
043 * </p>
044 * <p>
045 * Calling {@link #print(Object)} method will result:
046 *
047 * <pre>
048 * { null =&gt; '', boolean =&gt; 'true', number =&gt; '1', string =&gt; 'str',
049 *   binary =&gt; '010203', list =&gt; [ '1', '2', 'true' ] }
050 * </pre>
051 * </p>
052 */
053@InterfaceAudience.Private
054public final class JRubyFormat {
055  private static final Escaper escaper;
056
057  static {
058    escaper =
059      Escapers.builder().addEscape('\\', "\\\\").addEscape('\'', "\\'").addEscape('\n', "\\n")
060        .addEscape('\r', "\\r").addEscape('\t', "\\t").addEscape('\f', "\\f").build();
061  }
062
063  private JRubyFormat() {
064  }
065
066  private static String escape(Object object) {
067    if (object == null) {
068      return "";
069    } else {
070      return escaper.escape(object.toString());
071    }
072  }
073
074  @SuppressWarnings({ "unchecked" })
075  private static void appendJRuby(StringBuilder builder, Object object) {
076    if (object == null) {
077      builder.append("''");
078    } else if (object instanceof List) {
079      builder.append("[");
080
081      boolean first = true;
082
083      for (Object element : (List<Object>) object) {
084        if (first) {
085          first = false;
086          builder.append(" ");
087        } else {
088          builder.append(", ");
089        }
090
091        appendJRuby(builder, element);
092      }
093
094      if (!first) {
095        builder.append(" ");
096      }
097
098      builder.append("]");
099    } else if (object instanceof Map) {
100      builder.append("{");
101
102      boolean first = true;
103
104      for (Entry<String, Object> entry : ((Map<String, Object>) object).entrySet()) {
105        if (first) {
106          first = false;
107          builder.append(" ");
108        } else {
109          builder.append(", ");
110        }
111
112        String key = entry.getKey();
113        String escapedKey = escape(key);
114
115        if (key.equals(escapedKey)) {
116          builder.append(key);
117        } else {
118          builder.append("'").append(escapedKey).append("'");
119        }
120
121        builder.append(" => ");
122        appendJRuby(builder, entry.getValue());
123      }
124
125      if (!first) {
126        builder.append(" ");
127      }
128
129      builder.append("}");
130    } else if (object instanceof byte[]) {
131      String byteString = Bytes.toHex((byte[]) object);
132      builder.append("'").append(escape(byteString)).append("'");
133    } else {
134      builder.append("'").append(escape(object)).append("'");
135    }
136  }
137
138  public static String print(Object object) {
139    StringBuilder builder = new StringBuilder();
140
141    appendJRuby(builder, object);
142
143    return builder.toString();
144  }
145}