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}