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