001/*
002 * Syncany, www.syncany.org
003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com>
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation, either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018package org.syncany.chunk;
019
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.OutputStream;
023import java.util.Map;
024import java.util.logging.Level;
025import java.util.logging.Logger;
026
027import org.syncany.util.StringUtil;
028
029/**
030 * A transformer combines one or many stream-transforming {@link OutputStream}s and {@link InputStream}s.
031 * Implementations might provide functionality to encrypt or compress output streams, and to decrypt
032 * or uncompress a corresponding  input stream.
033 *
034 * <p>Transformers can be chained in order to allow multiple consecutive output-/input stream
035 * transformers to be applied to a stream.
036 *
037 * <p>A transformer can be instantiated using its implementation-specific constructor, or by calling
038 * its default constructor and initializing it using the {@link #init(Map) init()} method. Depending
039 * on the implementation, varying settings must be passed.
040 *
041 * @author Philipp C. Heckel (philipp.heckel@gmail.com)
042 */
043public abstract class Transformer {
044        private static final Logger logger = Logger.getLogger(Transformer.class.getSimpleName());
045        protected Transformer nextTransformer;
046
047        /**
048         * Creates a new transformer object (no next transformer)
049         */
050        public Transformer() {
051                this(null);
052        }
053
054        /**
055         * Create a new transformer, with a nested/chained transformer that will be 
056         * be applied after this transformer.
057         * 
058         * @param nextTransformer The next transformer (to be applied after this transformer)
059         */
060        public Transformer(Transformer nextTransformer) {
061                this.nextTransformer = nextTransformer;
062        }
063
064        /**
065         * If a transformer is instantiated via the default constructor (e.g. via a config file),
066         * it must be initialized using this method. The settings passed to the method depend
067         * on the implementation of the transformer.
068         *
069         * @param settings Implementation-specific setting map
070         * @throws Exception If the given settings are invalid or insufficient for instantiation
071         */
072        public abstract void init(Map<String, String> settings) throws Exception;
073
074        /**
075         * Create a stream-transforming {@link OutputStream}. Depending on the implementation, the
076         * bytes written to the output stream might be encrypted, compressed, etc.
077         *
078         * @param out Original output stream which is transformed by this transformer
079         * @return Returns a transformed output stream
080         * @throws IOException If an exception occurs when instantiating or writing to the stream
081         */
082        public abstract OutputStream createOutputStream(OutputStream out) throws IOException;
083
084        /**
085         * Creates a strea-transforming {@link InputStream}. Depending on the implementation, the
086         * bytes read from the input stream are uncompressed, decrypted, etc.
087         *  
088         * @param in Original input stream which is transformed by this transformer 
089         * @return Returns a transformed input stream
090         * @throws IOException If an exception occurs when instantiating or reading from the stream
091         */
092        public abstract InputStream createInputStream(InputStream in) throws IOException;
093
094        /**
095         * An implementation of a transformer must override this method to identify the 
096         * type of transformer and/or its settings.
097         */
098        @Override
099        public abstract String toString();
100
101        /**
102         * Instantiates a transformer by its name using the default constructor. After creating
103         * a new transformer, it must be initialized using the {@link #init(Map) init()} method.  
104         * 
105         * <p>The given type attribute is mapped to fully qualified class name (FQCN) of the form
106         * <code>org.syncany.chunk.XTransformer</code>, where <code>X</code> is the camel-cased type
107         * attribute.  
108         * 
109         * @param type Type/name of the transformer (corresponds to its camel case class name)
110         * @return Returns a new transformer
111         * @throws Exception If the FQCN cannot be found or the class cannot be instantiated
112         */
113        public static Transformer getInstance(String type) throws Exception {
114                String thisPackage = Transformer.class.getPackage().getName();
115                String camelCaseName = StringUtil.toCamelCase(type);
116                String fqClassName = thisPackage + "." + camelCaseName + Transformer.class.getSimpleName();
117
118                // Try to load!
119                try {
120                        Class<?> clazz = Class.forName(fqClassName);
121                        return (Transformer) clazz.newInstance();
122                }
123                catch (Exception ex) {
124                        logger.log(Level.INFO, "Could not find operation FQCN " + fqClassName, ex);
125                        return null;
126                }
127        }
128
129        public void setNextTransformer(Transformer nextTransformer) {
130                this.nextTransformer = nextTransformer;
131        }
132}