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, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.reef.tang.implementation.avro; 020 021import org.apache.avro.file.DataFileReader; 022import org.apache.avro.file.DataFileWriter; 023import org.apache.avro.io.*; 024import org.apache.avro.specific.SpecificDatumReader; 025import org.apache.avro.specific.SpecificDatumWriter; 026import org.apache.reef.tang.ClassHierarchy; 027import org.apache.reef.tang.ClassHierarchySerializer; 028import org.apache.reef.tang.types.*; 029 030import javax.inject.Inject; 031import java.io.*; 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.List; 035 036/** 037 * Serialize and Deserialize ClassHierarchy to and from AvroClassHierarchy. 038 * This class is stateless and is therefore safe to reuse. 039 */ 040public final class AvroClassHierarchySerializer implements ClassHierarchySerializer { 041 042 /** 043 * The Charset used for the JSON encoding. 044 */ 045 private static final String JSON_CHARSET = "ISO-8859-1"; 046 047 @Inject 048 public AvroClassHierarchySerializer() { 049 } 050 051 /** 052 * Serialize the ClassHierarchy into the AvroNode. 053 * @param ch ClassHierarchy to serialize 054 * @return a Avro node having class hierarchy 055 */ 056 public AvroNode toAvro(final ClassHierarchy ch) { 057 return newAvroNode(ch.getNamespace()); 058 } 059 060 /** 061 * Deserialize the ClassHierarchy from the AvroNode. 062 * @param n AvroNode to deserialize 063 * @return a class hierarchy 064 */ 065 public ClassHierarchy fromAvro(final AvroNode n) { 066 return new AvroClassHierarchy(n); 067 } 068 069 private AvroNode newAvroNode(final Node n) { 070 final List<AvroNode> children = new ArrayList<>(); 071 for (final Node child : n.getChildren()) { 072 children.add(newAvroNode(child)); 073 } 074 if (n instanceof ClassNode) { 075 final ClassNode<?> cn = (ClassNode<?>) n; 076 final ConstructorDef<?>[] injectable = cn.getInjectableConstructors(); 077 final ConstructorDef<?>[] all = cn.getAllConstructors(); 078 final List<ConstructorDef<?>> others = new ArrayList<>(Arrays.asList(all)); 079 others.removeAll(Arrays.asList(injectable)); 080 081 final List<AvroConstructorDef> injectableConstructors = new ArrayList<>(); 082 for (final ConstructorDef<?> inj : injectable) { 083 injectableConstructors.add(newConstructorDef(inj)); 084 } 085 final List<AvroConstructorDef> otherConstructors = new ArrayList<>(); 086 for (final ConstructorDef<?> other : others) { 087 otherConstructors.add(newConstructorDef(other)); 088 } 089 final List<CharSequence> implFullNames = new ArrayList<>(); 090 for (final ClassNode<?> impl : cn.getKnownImplementations()) { 091 implFullNames.add(impl.getFullName()); 092 } 093 return newClassNode(n.getName(), n.getFullName(), cn.isInjectionCandidate(), cn.isExternalConstructor(), 094 cn.isUnit(), injectableConstructors, otherConstructors, implFullNames, cn.getDefaultImplementation(), 095 children); 096 } else if (n instanceof NamedParameterNode) { 097 final NamedParameterNode<?> np = (NamedParameterNode<?>) n; 098 final List<CharSequence> defaultInstances = new ArrayList<>(); 099 for (final CharSequence defaultInstance : np.getDefaultInstanceAsStrings()) { 100 defaultInstances.add(defaultInstance); 101 } 102 return newNamedParameterNode(np.getName(), np.getFullName(), np.getSimpleArgName(), np.getFullArgName(), 103 np.isSet(), np.isList(), np.getDocumentation(), np.getShortName(), defaultInstances, children); 104 } else if (n instanceof PackageNode) { 105 return newPackageNode(n.getName(), n.getFullName(), children); 106 } else { 107 throw new IllegalStateException("Encountered unknown type of Node: " + n); 108 } 109 } 110 111 private AvroNode newClassNode(final String name, 112 final String fullName, 113 final boolean isInjectionCandidate, 114 final boolean isExternalConstructor, 115 final boolean isUnit, 116 final List<AvroConstructorDef> injectableConstructors, 117 final List<AvroConstructorDef> otherConstructors, 118 final List<CharSequence> implFullNames, 119 final String defaultImplementation, 120 final List<AvroNode> children) { 121 return AvroNode.newBuilder() 122 .setName(name) 123 .setFullName(fullName) 124 .setClassNode(AvroClassNode.newBuilder() 125 .setIsInjectionCandidate(isInjectionCandidate) 126 .setIsExternalConstructor(isExternalConstructor) 127 .setIsUnit(isUnit) 128 .setInjectableConstructors(injectableConstructors) 129 .setOtherConstructors(otherConstructors) 130 .setImplFullNames(implFullNames) 131 .setDefaultImplementation(defaultImplementation) 132 .build()) 133 .setChildren(children).build(); 134 } 135 136 private AvroNode newNamedParameterNode(final String name, 137 final String fullName, 138 final String simpleArgClassName, 139 final String fullArgClassName, 140 final boolean isSet, 141 final boolean isList, 142 final String documentation, 143 final String shortName, 144 final List<CharSequence> instanceDefault, 145 final List<AvroNode> children) { 146 147 return AvroNode.newBuilder() 148 .setName(name) 149 .setFullName(fullName) 150 .setNamedParameterNode(AvroNamedParameterNode.newBuilder() 151 .setSimpleArgClassName(simpleArgClassName) 152 .setFullArgClassName(fullArgClassName) 153 .setIsSet(isSet) 154 .setIsList(isList) 155 .setDocumentation(documentation) 156 .setShortName(shortName) 157 .setInstanceDefault(instanceDefault) 158 .build()) 159 .setChildren(children).build(); 160 } 161 162 private AvroNode newPackageNode(final String name, 163 final String fullName, 164 final List<AvroNode> children) { 165 return AvroNode.newBuilder() 166 .setPackageNode(AvroPackageNode.newBuilder().build()) 167 .setName(name).setFullName(fullName).setChildren(children).build(); 168 } 169 170 private AvroConstructorArg newConstructorArg(final String fullArgClassName, 171 final String namedParameterName, 172 final boolean isFuture) { 173 return AvroConstructorArg.newBuilder() 174 .setFullArgClassName(fullArgClassName) 175 .setIsInjectionFuture(isFuture) 176 .setNamedParameterName(namedParameterName).build(); 177 } 178 179 private AvroConstructorDef newConstructorDef(final ConstructorDef<?> def) { 180 final List<AvroConstructorArg> args = new ArrayList<>(); 181 for (final ConstructorArg arg : def.getArgs()) { 182 args.add(newConstructorArg(arg.getType(), arg.getNamedParameterName(), arg.isInjectionFuture())); 183 } 184 return AvroConstructorDef.newBuilder() 185 .setFullClassName(def.getClassName()) 186 .setConstructorArgs(args).build(); 187 } 188 189 @Override 190 public void toFile(final ClassHierarchy classHierarchy, final File file) throws IOException { 191 final AvroNode avroNode = toAvro(classHierarchy); 192 final DatumWriter<AvroNode> avroNodeWriter = new SpecificDatumWriter<>(AvroNode.class); 193 try (final DataFileWriter<AvroNode> dataFileWriter = new DataFileWriter<>(avroNodeWriter)) { 194 dataFileWriter.create(avroNode.getSchema(), file); 195 dataFileWriter.append(avroNode); 196 } 197 } 198 199 @Override 200 public byte[] toByteArray(final ClassHierarchy classHierarchy) throws IOException { 201 final DatumWriter<AvroNode> requestWriter = new SpecificDatumWriter<>(AvroNode.class); 202 try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) { 203 final BinaryEncoder encoder = EncoderFactory.get().binaryEncoder(out, null); 204 requestWriter.write(toAvro(classHierarchy), encoder); 205 encoder.flush(); 206 return out.toByteArray(); 207 } 208 } 209 210 @Override 211 public String toString(final ClassHierarchy classHierarchy) throws IOException { 212 final DatumWriter<AvroNode> classHierarchyWriter = new SpecificDatumWriter<>(AvroNode.class); 213 try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) { 214 final JsonEncoder encoder = EncoderFactory.get().jsonEncoder(AvroNode.SCHEMA$, out); 215 classHierarchyWriter.write(toAvro(classHierarchy), encoder); 216 encoder.flush(); 217 out.flush(); 218 return out.toString(JSON_CHARSET); 219 } 220 } 221 222 @Override 223 public void toTextFile(final ClassHierarchy classHierarchy, final File file) throws IOException { 224 try (final Writer w = new FileWriter(file)) { 225 w.write(this.toString(classHierarchy)); 226 } 227 } 228 229 @Override 230 public ClassHierarchy fromFile(final File file) throws IOException { 231 final AvroNode avroNode; 232 try (final DataFileReader<AvroNode> dataFileReader = 233 new DataFileReader<>(file, new SpecificDatumReader<>(AvroNode.class))) { 234 avroNode = dataFileReader.next(); 235 } 236 return fromAvro(avroNode); 237 } 238 239 @Override 240 public ClassHierarchy fromByteArray(final byte[] theBytes) throws IOException { 241 final BinaryDecoder decoder = DecoderFactory.get().binaryDecoder(theBytes, null); 242 final SpecificDatumReader<AvroNode> reader = new SpecificDatumReader<>(AvroNode.class); 243 return fromAvro(reader.read(null, decoder)); 244 } 245 246 @Override 247 public ClassHierarchy fromString(final String theString) throws IOException { 248 final JsonDecoder decoder = DecoderFactory.get().jsonDecoder(AvroNode.getClassSchema(), theString); 249 final SpecificDatumReader<AvroNode> reader = new SpecificDatumReader<>(AvroNode.class); 250 return fromAvro(reader.read(null, decoder)); 251 } 252 253 @Override 254 public ClassHierarchy fromTextFile(final File file) throws IOException { 255 final StringBuilder stringBuilder = new StringBuilder(); 256 try (final BufferedReader reader = new BufferedReader(new FileReader(file))) { 257 String line = reader.readLine(); 258 while (line != null) { 259 stringBuilder.append(line); 260 line = reader.readLine(); 261 } 262 } 263 return fromString(stringBuilder.toString()); 264 } 265}