001    // BSD License (http://www.galagosearch.org/license)
002    package org.galagosearch.tupleflow.typebuilder;
003    
004    import java.util.ArrayList;
005    import java.util.Collections;
006    import java.util.HashMap;
007    import java.util.HashSet;
008    import org.antlr.stringtemplate.CommonGroupLoader;
009    import org.antlr.stringtemplate.StringTemplate;
010    import org.antlr.stringtemplate.StringTemplateGroup;
011    import org.antlr.stringtemplate.language.AngleBracketTemplateLexer;
012    import org.galagosearch.tupleflow.typebuilder.FieldSpecification.DataType;
013    
014    /**
015     *
016     * @author trevor
017     */
018    public class TemplateTypeBuilder {
019        StringTemplateGroup template;
020        String typeName;
021        String typePackage;
022        ArrayList<Field> typeFields;
023        ArrayList<OrderSpec> typeOrders;
024    
025        /**
026         * For an array master, returns
027         * an array containing the last master.length-index elements.
028         */
029        public static String[] subarray(String[] master, int index) {
030            if (master.length <= index) {
031                return new String[0];
032            } else {
033                String[] sub = new String[master.length - index];
034                System.arraycopy(master, index, sub, 0, sub.length);
035                return sub;
036            }
037        }
038    
039        /**
040         * Returns a string containing all the elements of args, space delimited.
041         */
042        public static String join(String[] args, String delimiter) {
043            String output = "";
044            StringBuilder builder = new StringBuilder();
045    
046            for (String arg : args) {
047                if (builder.length() > 0) {
048                    builder.append(delimiter);
049                }
050                builder.append(arg);
051            }
052    
053            return builder.toString();
054        }
055    
056        public static String join(String[] args) {
057            return join(args, " ");
058        }
059    
060        public static String caps(String input) {
061            if (input.length() == 0) {
062                return input;
063            }
064            char first = Character.toUpperCase(input.charAt(0));
065            return "" + first + input.substring(1);
066        }
067    
068        public static String plural(String input) {
069            return input + "s";
070        }
071    
072        public static class Field {
073            public Field(DataType type, String name) {
074                this.dataType = type;
075                this.type = type.getType();
076                this.name = name;
077                this.capsName = caps(name);
078                this.pluralName = plural(name);
079                
080                this.inputType = caps(type.getInternalType());
081                this.baseType = type.getBaseType();
082                this.classTypeName = type.getClassName();
083                this.isArray = dataType.isArray();
084            }
085            public DataType dataType;
086            public String type;
087            public String name;
088            public String baseType;
089            public boolean isInteger;
090            public boolean isString;
091            public boolean isArray;
092            public String classTypeName;
093            public String capsName;
094            public String inputType;
095            public String pluralName;
096        }
097    
098        public static class OrderedField extends Field {
099            public OrderedField(Field field, boolean ascending, boolean delta, boolean runLengthEncoded) {
100                this(field.dataType, field.name, ascending, delta, runLengthEncoded);
101            }
102    
103            public OrderedField(DataType type, String name, boolean ascending, boolean delta, boolean runLengthEncoded) {
104                super(type, name);
105                this.ascending = ascending;
106                this.direction = ascending ? "+" : "-";
107                this.directionName = ascending ? "Ascending" : "Descending";
108                this.runLengthEncoded = runLengthEncoded;
109                this.delta = delta;
110            }
111            public boolean ascending;
112            public String direction;
113            public String directionName;
114            public boolean delta;
115            public boolean runLengthEncoded;
116            public ArrayList<OrderedField> remaining = new ArrayList();
117        }
118    
119        public static class OrderSpec {
120            public OrderSpec(OrderSpecification spec, ArrayList<Field> allFields) {
121                this.allFields = allFields;
122    
123                HashMap<String, Field> fieldMap = new HashMap();
124                HashSet<String> orderedNames = new HashSet();
125    
126                for (Field f : allFields) {
127                    fieldMap.put(f.name, f);
128                }
129    
130                ArrayList<OrderedFieldSpecification> inputFields = spec.getOrderedFields();
131                
132                for (int i = 0; i < inputFields.size(); i++) {
133                    Field field = fieldMap.get(inputFields.get(i).getName());
134                    boolean isLastField = ((inputFields.size() - i) == 1);
135                    // BUGBUG: this is where delta encoding should go
136                    boolean useDelta = false; //isLastField && (field.isInteger || field.isString); 
137                    boolean useRLE = !useDelta;
138                    boolean isAscending = (inputFields.get(i).getDirection() == Direction.ASCENDING);
139                    String fieldName = inputFields.get(i).getName();
140    
141                    if (fieldMap.get(fieldName) == null) {
142                        throw new RuntimeException("'" + fieldName + "' is specified in an order statement, " +
143                                                   "but it isn't a type field name.");
144                    }
145                    
146                    OrderedField ordered = new OrderedField(fieldMap.get(fieldName),
147                                                            isAscending, useDelta, useRLE);
148                    orderedFields.add(ordered);
149                    orderedNames.add(fieldName);
150                }
151    
152                for (int i = 0; i < inputFields.size(); i++) {
153                    orderedFields.get(i).remaining.addAll(
154                            orderedFields.subList(i + 1, orderedFields.size()));
155                }
156    
157                backwardOrderedFields.addAll(orderedFields);
158                Collections.reverse(backwardOrderedFields);
159    
160                for (int i = 0; i < orderedFields.size(); i++) {
161                    if (orderedFields.get(i).runLengthEncoded) {
162                        rleFields.add(orderedFields.get(i));
163                    }
164                    if (orderedFields.get(i).delta) {
165                        deltaFields.add(orderedFields.get(i));
166                    }
167                }
168    
169                for (int i = 0; i < orderedFields.size(); i++) {
170                    OrderedField current = orderedFields.get(i);
171                    OrderedField next = ((i + 1) < orderedFields.size()) ? orderedFields.get(i + 1) : null;
172                    OrderedField previous = (i != 0) ? orderedFields.get(i - 1) : null;
173    
174                    fieldPairs.add(new FieldPair(current, next, previous));
175                }
176    
177                for (int i = 0; i < allFields.size(); i++) {
178                    if (orderedNames.contains(allFields.get(i).name)) {
179                        continue;
180                    }
181                    unorderedFields.add(allFields.get(i));
182                }
183            }
184    
185            public String getClassName() {
186                StringBuilder builder = new StringBuilder();
187    
188                if (orderedFields.size() > 0) {
189                    for (OrderedField orderedField : orderedFields) {
190                        if (!orderedField.ascending) {
191                            builder.append("Desc");
192                        }
193                        builder.append(caps(orderedField.name));
194                    }
195    
196                    builder.append("Order");
197                } else {
198                    builder.append("Unordered");
199                }
200    
201                return builder.toString();
202            }
203    
204            public static class FieldPair {
205                public FieldPair(OrderedField c, OrderedField n, OrderedField p) {
206                    current = c;
207                    next = n;
208                    previous = p;
209                }
210                public OrderedField current;
211                public OrderedField next;
212                public OrderedField previous;
213            }
214            public ArrayList<OrderedField> orderedFields = new ArrayList();
215            public ArrayList<OrderedField> backwardOrderedFields = new ArrayList();
216            public ArrayList<FieldPair> fieldPairs = new ArrayList();
217            public ArrayList<OrderedField> rleFields = new ArrayList();
218            public ArrayList<Field> unorderedFields = new ArrayList();
219            public ArrayList<OrderedField> deltaFields = new ArrayList();
220            public ArrayList<Field> allFields;
221        }
222    
223        /**
224         * Returns the text of a Type<T> object for the T class.
225         */
226        @Override
227        public String toString() {
228            StringTemplate t = template.getInstanceOf("type");
229            HashMap<String, Object> map = new HashMap<String, Object>();
230            map.put("typeName", this.typeName);
231            map.put("package", this.typePackage);
232            map.put("orders", this.typeOrders);
233            map.put("fields", this.typeFields);
234    
235            boolean containsArray = false;
236            for (Field f : typeFields) {
237                containsArray = containsArray || f.isArray;
238            }
239            map.put("containsArray", containsArray);
240            t.setAttributes(map);
241            return t.toString();
242        }
243    
244        public TemplateTypeBuilder(TypeSpecification spec) {
245            CommonGroupLoader loader = new CommonGroupLoader("org/galagosearch/tupleflow/templates", null);
246            StringTemplateGroup.registerGroupLoader(loader);
247            StringTemplateGroup.registerDefaultLexer(AngleBracketTemplateLexer.class);
248            this.template = StringTemplateGroup.loadGroup("GalagoType");
249    
250            this.typeName = spec.getTypeName();
251            this.typePackage = spec.getPackageName();
252            this.typeFields = new ArrayList<Field>();
253            this.typeOrders = new ArrayList<OrderSpec>();
254    
255            for (FieldSpecification fieldSpec : spec.getFields()) {
256                Field field = new Field(fieldSpec.getType(), fieldSpec.getName());
257                typeFields.add(field);
258            }
259    
260            for (OrderSpecification orderSpec : spec.getOrders()) {
261                OrderSpec order = new OrderSpec(orderSpec, typeFields);
262                typeOrders.add(order);
263            }
264        }
265        
266        public static void main(String[] args) throws java.lang.Exception {
267            for (String fileName : args) {
268                TypeSpecification spec = ParserDriver.getTypeSpecification(fileName);
269                TemplateTypeBuilder builder = new TemplateTypeBuilder(spec);
270                
271                java.io.FileWriter writer = new java.io.FileWriter(spec.getTypeName() + ".java");
272                String comment = "// This file was automatically generated with the command: \n" +
273                             "//     java org.galagosearch.tupleflow.typebuilder.TemplateTypeBuilder ...\n";
274    
275                writer.write(comment);
276                writer.write(builder.toString());
277                writer.close();
278            }
279        }
280    }