/** * Copyright 2011-2017 Asakusa Framework Team. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.asakusafw.runtime.io; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Constructor; import java.text.MessageFormat; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.asakusafw.runtime.model.ModelInputLocation; import com.asakusafw.runtime.model.ModelOutputLocation; /** * An abstract super class of reading/writing data model objects. * @param <T> the target data model type * @since 0.1.0 */ public abstract class ModelIoFactory<T> { static final Log LOG = LogFactory.getLog(ModelIoFactory.class); /** * The qualified data model class name pattern in regex. * <ul> * <li> <code>$1</code> - base package name </li> * <li> <code>$2</code> - simple class name </li> * </ul> */ static final Pattern MODEL_CLASS_NAME_PATTERN = Pattern.compile( "(.*)\\.model\\.([^\\.]+)$"); //$NON-NLS-1$ /** * The qualified name pattern of {@link ModelInput} in MessageFormat. * <ul> * <li> <code>{0}</code> - base package name </li> * <li> <code>{1}</code> - simple class name </li> * </ul> * @deprecated Use {@link ModelInputLocation} to specify {@link ModelInput} instead */ @Deprecated public static final String MODEL_INPUT_CLASS_FORMAT = "{0}.io.{1}ModelInput"; //$NON-NLS-1$ /** * The qualified name pattern of {@link ModelOutput} in MessageFormat. * <ul> * <li> <code>{0}</code> - base package name </li> * <li> <code>{1}</code> - simple class name </li> * </ul> * @deprecated Use {@link ModelOutputLocation} to specify {@link ModelOutput} instead */ @Deprecated public static final String MODEL_OUTPUT_CLASS_FORMAT = "{0}.io.{1}ModelOutput"; //$NON-NLS-1$ private final Class<T> modelClass; /** * Creates a new instance. * @param modelClass the data model class * @throws IllegalArgumentException if the parameter is {@code null} */ public ModelIoFactory(Class<T> modelClass) { if (modelClass == null) { throw new IllegalArgumentException("modelClass must not be null"); //$NON-NLS-1$ } this.modelClass = modelClass; } /** * Returns the target data model class. * @return the target data model class */ protected Class<T> getModelClass() { return modelClass; } /** * Creates a new data model object. * @return the created object * @throws IOException if failed to create a new object */ public T createModelObject() throws IOException { try { return modelClass.newInstance(); } catch (Exception e) { throw new IOException(MessageFormat.format( "Cannot create a new model object for {0}", getClass().getName()), e); } } /** * Creates a new {@link ModelInput} for the target {@link InputStream}. * @param in an input stream that provides serialized data model objects * @return the created instance * @throws IOException if failed to initialize the {@code ModelInput} * @throws IllegalArgumentException if the parameter is {@code null} */ public ModelInput<T> createModelInput(InputStream in) throws IOException { if (in == null) { throw new IllegalArgumentException("in must not be null"); //$NON-NLS-1$ } RecordParser parser = createRecordParser(in); return createModelInput(parser); } /** * Creates a new {@link ModelInput} for the target {@link RecordParser}. * @param parser a parser that provides records of data model objects * @return the created instance * @throws IOException if failed to initialize the {@code ModelInput} * @throws IllegalArgumentException if the parameter is {@code null} */ public ModelInput<T> createModelInput(RecordParser parser) throws IOException { if (parser == null) { throw new IllegalArgumentException("parser must not be null"); //$NON-NLS-1$ } Class<?> inputClass; try { inputClass = findModelInputClass(); } catch (ClassNotFoundException e) { throw new IOException(MessageFormat.format( "Cannot find a model input for {0}", modelClass.getName()), e); } try { Constructor<?> ctor = inputClass.getConstructor(RecordParser.class); @SuppressWarnings("unchecked") ModelInput<T> instance = (ModelInput<T>) ctor.newInstance(parser); return instance; } catch (Exception e) { throw new IOException(MessageFormat.format( "Cannot initialize a model input for {0}", modelClass.getName()), e); } } /** * Creates a new {@link ModelOutput} for the target {@link OutputStream}. * @param out an output stream that accepts serialized data model objects * @return the created instance * @throws IOException if failed to initialize the {@code ModelOutput} * @throws IllegalArgumentException if the parameter is {@code null} */ public ModelOutput<T> createModelOutput(OutputStream out) throws IOException { if (out == null) { throw new IllegalArgumentException("out must not be null"); //$NON-NLS-1$ } RecordEmitter emitter = createRecordEmitter(out); return createModelOutput(emitter); } /** * Creates a new {@link ModelOutput} for the target {@link RecordEmitter}. * @param emitter an emitter that accepts data model objects * @return the created instance * @throws IOException if failed to initialize the {@code ModelOutput} * @throws IllegalArgumentException if the parameter is {@code null} */ public ModelOutput<T> createModelOutput(RecordEmitter emitter) throws IOException { if (emitter == null) { throw new IllegalArgumentException("emitter must not be null"); //$NON-NLS-1$ } Class<?> outputClass; try { outputClass = findModelOutputClass(); } catch (ClassNotFoundException e) { throw new IOException(MessageFormat.format( "Cannot find a model output for {0}", modelClass.getName()), e); } try { Constructor<?> ctor = outputClass.getConstructor(RecordEmitter.class); @SuppressWarnings("unchecked") ModelOutput<T> instance = (ModelOutput<T>) ctor.newInstance(emitter); return instance; } catch (Exception e) { throw new IOException(MessageFormat.format( "Cannot initialize a model output for {0}", modelClass.getName()), e); } } /** * Creates a new default {@link RecordParser}. * @param in the source input * @return the created object * @throws IOException if failed to initialize the parser * @throws IllegalArgumentException if the parameter is {@code null} */ protected abstract RecordParser createRecordParser(InputStream in) throws IOException; /** * Creates a new default {@link RecordEmitter}. * @param out the target output * @return the created object * @throws IOException if failed to initialize the emitter * @throws IllegalArgumentException if the parameter {@code null} */ protected abstract RecordEmitter createRecordEmitter(OutputStream out) throws IOException; /** * Returns the {@link ModelInput} class for this factory. * @return the {@link ModelInput} class * @throws ClassNotFoundException if the target class is not found * @see ModelInputLocation */ protected Class<?> findModelInputClass() throws ClassNotFoundException { ModelInputLocation annotation = modelClass.getAnnotation(ModelInputLocation.class); if (annotation != null) { return annotation.value(); } LOG.warn(MessageFormat.format( "Data model class \"{0}\" does not have annotation \"{1}\"", modelClass.getName(), ModelInputLocation.class.getName(), ModelInput.class.getSimpleName())); return findClassFromModel(MODEL_INPUT_CLASS_FORMAT); } /** * Returns the {@link ModelOutput} class for this factory. * @return the {@link ModelOutput} class * @throws ClassNotFoundException if the target class is not found * @see ModelInputLocation */ protected Class<?> findModelOutputClass() throws ClassNotFoundException { ModelOutputLocation annotation = modelClass.getAnnotation(ModelOutputLocation.class); if (annotation != null) { return annotation.value(); } LOG.warn(MessageFormat.format( "Data model class \"{0}\" does not have annotation \"{1}\"", modelClass.getName(), ModelOutputLocation.class.getName(), ModelOutput.class.getSimpleName())); return findClassFromModel(MODEL_OUTPUT_CLASS_FORMAT); } private Class<?> findClassFromModel(String format) throws ClassNotFoundException { Matcher m = MODEL_CLASS_NAME_PATTERN.matcher(modelClass.getName()); if (m.matches() == false) { throw new ClassNotFoundException(MessageFormat.format( "Invalid model class name pattern: {0}", modelClass.getName())); } String qualifier = m.group(1); String simpleName = m.group(2); String result = MessageFormat.format(format, qualifier, simpleName); return Class.forName(result, false, modelClass.getClassLoader()); } }