1/* 2 * Copyright (C) 2010 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.clearsilver.jsilver.data; 18 19import java.io.IOException; 20import java.util.ArrayList; 21import java.util.List; 22import java.util.logging.Logger; 23 24/** 25 * Implementation of Data that allows for multiple underlying Data objects and checks each one in 26 * order for a value before giving up. Behaves like local HDF and global HDF in the JNI 27 * implementation of Clearsilver. This is only meant to be a root Data object and hardcodes that 28 * fact. 29 * <p> 30 * Note: If you have elements foo.1, foo.2, foo.3 in first Data object and foo.4, foo.5, foo.6 in 31 * second Data object, then fetching children of foo will return only foo.1 foo.2 foo.3 from first 32 * Data object. 33 */ 34public class ChainedData extends DelegatedData { 35 public static final Logger logger = Logger.getLogger(ChainedData.class.getName()); 36 37 // This mode allows developers to locate occurrences where they set the same HDF 38 // variable in multiple Data objects in the chain, which usually indicates 39 // bad planning or misuse. 40 public static final boolean DEBUG_MULTIPLE_ASSIGNMENTS = false; 41 42 Data[] dataList; 43 44 /** 45 * Optmization for case of single item. 46 * 47 * @param data a single data object to wrap. 48 */ 49 public ChainedData(Data data) { 50 super(data); 51 this.dataList = new Data[] {data}; 52 } 53 54 public ChainedData(Data... dataList) { 55 super(getFirstData(dataList)); 56 this.dataList = dataList; 57 } 58 59 public ChainedData(List<Data> dataList) { 60 super(getFirstData(dataList)); 61 this.dataList = dataList.toArray(new Data[dataList.size()]); 62 } 63 64 @Override 65 protected DelegatedData newInstance(Data newDelegate) { 66 return newDelegate == null ? null : new ChainedData(newDelegate); 67 } 68 69 private static Data getFirstData(Data[] dataList) { 70 if (dataList.length == 0) { 71 throw new IllegalArgumentException("Must pass in at least one Data object to ChainedData."); 72 } 73 Data first = dataList[0]; 74 if (first == null) { 75 throw new IllegalArgumentException("ChainedData does not accept null Data objects."); 76 } 77 return first; 78 } 79 80 private static Data getFirstData(List<Data> dataList) { 81 if (dataList.size() == 0) { 82 throw new IllegalArgumentException("Must pass in at least one Data object to ChainedData."); 83 } 84 Data first = dataList.get(0); 85 if (first == null) { 86 throw new IllegalArgumentException("ChainedData does not accept null Data objects."); 87 } 88 return first; 89 } 90 91 @Override 92 public Data getChild(String path) { 93 ArrayList<Data> children = null; 94 Data first = null; 95 for (Data d : dataList) { 96 Data child = d.getChild(path); 97 if (child != null) { 98 if (!DEBUG_MULTIPLE_ASSIGNMENTS) { 99 // If not in debug mode just return the first match. This assumes we are using the new 100 // style of VariableLocator that does not iteratively ask for each HDF path element 101 // separately. 102 return child; 103 } 104 if (first == null) { 105 // First match found 106 first = child; 107 } else if (children == null) { 108 // Second match found 109 children = new ArrayList<Data>(dataList.length); 110 children.add(first); 111 children.add(child); 112 } else { 113 // Third or more match found 114 children.add(child); 115 } 116 } 117 } 118 if (children == null) { 119 // 0 or 1 matches. Return first which is null or Data. 120 return first; 121 } else { 122 // Multiple matches. Pass back the first item found. This is only hit when 123 // DEBUG_MULTIPLE_ASSIGNMENTS is true. 124 logger.info("Found " + children.size() + " matches for path " + path); 125 return first; 126 } 127 } 128 129 @Override 130 public Data createChild(String path) { 131 Data child = getChild(path); 132 if (child != null) { 133 return child; 134 } else { 135 // We don't call super because we don't want to wrap the result in DelegatedData. 136 return dataList[0].createChild(path); 137 } 138 } 139 140 @Override 141 public String getValue(String path, String defaultValue) { 142 Data child = getChild(path); 143 if (child != null && child.getValue() != null) { 144 return child.getValue(); 145 } else { 146 return defaultValue; 147 } 148 } 149 150 @Override 151 public int getIntValue(String path, int defaultValue) { 152 Data child = getChild(path); 153 if (child != null) { 154 String value = child.getValue(); 155 try { 156 return value == null ? defaultValue : TypeConverter.parseNumber(value); 157 } catch (NumberFormatException e) { 158 return defaultValue; 159 } 160 } else { 161 return defaultValue; 162 } 163 } 164 165 @Override 166 public String getValue(String path) { 167 Data child = getChild(path); 168 if (child != null) { 169 return child.getValue(); 170 } else { 171 return null; 172 } 173 } 174 175 @Override 176 public int getIntValue(String path) { 177 Data child = getChild(path); 178 if (child != null) { 179 return child.getIntValue(); 180 } else { 181 return 0; 182 } 183 } 184 185 @Override 186 public boolean getBooleanValue(String path) { 187 Data child = getChild(path); 188 if (child != null) { 189 return child.getBooleanValue(); 190 } else { 191 return false; 192 } 193 } 194 195 @Override 196 public void toString(StringBuilder out, int indent) { 197 for (Data d : dataList) { 198 d.toString(out, indent); 199 } 200 } 201 202 @Override 203 public void write(Appendable out, int indent) throws IOException { 204 for (Data d : dataList) { 205 d.write(out, indent); 206 } 207 } 208 209 @Override 210 public void optimize() { 211 for (Data d : dataList) { 212 d.optimize(); 213 } 214 } 215} 216