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 
17 package org.clearsilver.jni;
18 
19 import org.clearsilver.CSFileLoader;
20 import org.clearsilver.HDF;
21 
22 import java.io.IOException;
23 import java.util.Calendar;
24 import java.util.Date;
25 import java.util.TimeZone;
26 
27 /**
28  * This class is a wrapper around the HDF C API.  Many features of the C API
29  * are not yet exposed through this wrapper.
30  */
31 public class JniHdf implements HDF {
32 
33   long hdfptr;  // stores the C HDF* pointer
34   JniHdf root; // If this is a child HDF node, points at the root node of
35                // the tree.  For root nodes this is null.  A child node needs
36                // to hold a reference on the root to prevent the root from
37                // being GC-ed.
38 
39   static {
JNI.loadLibrary()40     JNI.loadLibrary();
41   }
42 
cast(HDF hdf)43   static JniHdf cast(HDF hdf) {
44     if (!(hdf instanceof JniHdf)) {
45       throw new IllegalArgumentException("HDF object not of type JniHdf.  "
46           + "Make sure you use the same ClearsilverFactory to construct all "
47           + "related HDF and CS objects.");
48     }
49     return (JniHdf)hdf;
50   }
51 
52   /**
53    * Default public constructor.
54    */
JniHdf()55   public JniHdf() {
56     hdfptr = _init();
57     root = null;
58   }
59 
JniHdf(long hdfptr, JniHdf parent)60   protected JniHdf(long hdfptr, JniHdf parent) {
61     this.hdfptr = hdfptr;
62     this.root = (parent.root != null) ? parent.root : parent;
63   }
64 
65   /** Constructs an HDF child node.  Used by other methods in this class when
66    * a child node needs to be constructed.
67    */
newHdf(long hdfptr, HDF parent)68   protected JniHdf newHdf(long hdfptr, HDF parent) {
69     return new JniHdf(hdfptr, cast(parent));
70   }
71 
72   /** Clean up allocated memory if neccesary. close() allows application
73    *  to force clean up.
74    */
close()75   public void close() {
76     // Only root nodes have ownership of the C HDF pointer, so only a root
77     // node needs to dealloc hdfptr.dir
78     if (root == null) {
79       if (hdfptr != 0) {
80         _dealloc(hdfptr);
81         hdfptr = 0;
82       }
83     }
84   }
85 
86   /** Call close() just in case when deallocating Java object.
87    */
finalize()88   protected void finalize() throws Throwable {
89     close();
90     super.finalize();
91   }
92 
93   /** Loads the contents of the specified HDF file from disk into the current
94    *  HDF object.  The loaded contents are merged with the existing contents.
95    *  @param filename the name of file to read in and parse.
96    *  @throws java.io.FileNotFoundException if the specified file does not
97    *  exist.
98    *  @throws IOException other problems reading the file.
99    */
readFile(String filename)100   public boolean readFile(String filename) throws IOException {
101     if (hdfptr == 0) {
102       throw new NullPointerException("HDF is closed.");
103     }
104     return _readFile(hdfptr, filename, fileLoader != null);
105   }
106 
fileLoad(String filename)107   protected String fileLoad(String filename) throws IOException {
108     if (hdfptr == 0) {
109       throw new NullPointerException("HDF is closed.");
110     }
111     CSFileLoader aFileLoader = fileLoader;
112     if (aFileLoader == null) {
113       throw new NullPointerException("No fileLoader specified.");
114     } else {
115       String result = aFileLoader.load(this, filename);
116       if (result == null) {
117         throw new NullPointerException("CSFileLoader.load() returned null");
118       }
119       return result;
120     }
121   }
122 
123   // The optional CS file loader to use to read in files
124   private CSFileLoader fileLoader = null;
125 
126   /**
127    * Get the file loader in use, if any.
128    * @return the file loader in use.
129    */
getFileLoader()130   public CSFileLoader getFileLoader() {
131     return fileLoader;
132   }
133 
134   /**
135    * Set the CS file loader to use
136    * @param fileLoader the file loader that should be used.
137    */
setFileLoader(CSFileLoader fileLoader)138   public void setFileLoader(CSFileLoader fileLoader) {
139     this.fileLoader = fileLoader;
140   }
141 
142   /** Serializes HDF contents to a file (readable by readFile)
143    */
writeFile(String filename)144   public boolean writeFile(String filename) throws IOException {
145     if (hdfptr == 0) {
146       throw new NullPointerException("HDF is closed.");
147     }
148     return _writeFile(hdfptr, filename);
149   }
150 
151   /** Parses/loads the contents of the given string as HDF into the current
152    *  HDF object.  The loaded contents are merged with the existing contents.
153    */
readString(String data)154   public boolean readString(String data) {
155     if (hdfptr == 0) {
156       throw new NullPointerException("HDF is closed.");
157     }
158     return _readString(hdfptr, data);
159   }
160 
161   /** Serializes HDF contents to a string (readable by readString)
162    */
writeString()163   public String writeString() {
164     if (hdfptr == 0) {
165       throw new NullPointerException("HDF is closed.");
166     }
167     return _writeString(hdfptr);
168   }
169 
170   /** Retrieves the integer value at the specified path in this HDF node's
171    *  subtree.  If the value does not exist, or cannot be converted to an
172    *  integer, default_value will be returned. */
getIntValue(String hdfname, int default_value)173   public int getIntValue(String hdfname, int default_value) {
174     if (hdfptr == 0) {
175       throw new NullPointerException("HDF is closed.");
176     }
177     return _getIntValue(hdfptr,hdfname,default_value);
178   }
179 
180   /** Retrieves the value at the specified path in this HDF node's subtree.
181    */
getValue(String hdfname, String default_value)182   public String getValue(String hdfname, String default_value) {
183     if (hdfptr == 0) {
184       throw new NullPointerException("HDF is closed.");
185     }
186     return _getValue(hdfptr,hdfname,default_value);
187   }
188 
189   /** Sets the value at the specified path in this HDF node's subtree. */
setValue(String hdfname, String value)190   public void setValue(String hdfname, String value) {
191     if (hdfptr == 0) {
192       throw new NullPointerException("HDF is closed.");
193     }
194     _setValue(hdfptr,hdfname,value);
195   }
196 
197   /** Remove the specified subtree. */
removeTree(String hdfname)198   public void removeTree(String hdfname) {
199     if (hdfptr == 0) {
200       throw new NullPointerException("HDF is closed.");
201     }
202     _removeTree(hdfptr,hdfname);
203   }
204 
205   /** Links the src hdf name to the dest. */
setSymLink(String hdf_name_src, String hdf_name_dest)206   public void setSymLink(String hdf_name_src, String hdf_name_dest) {
207     if (hdfptr == 0) {
208       throw new NullPointerException("HDF is closed.");
209     }
210     _setSymLink(hdfptr,hdf_name_src,hdf_name_dest);
211   }
212 
213   /** Export a date to a clearsilver tree using a specified timezone */
exportDate(String hdfname, TimeZone timeZone, Date date)214   public void exportDate(String hdfname, TimeZone timeZone, Date date) {
215     if (hdfptr == 0) {
216       throw new NullPointerException("HDF is closed.");
217     }
218 
219     Calendar cal = Calendar.getInstance(timeZone);
220     cal.setTime(date);
221 
222     String sec = Integer.toString(cal.get(Calendar.SECOND));
223     setValue(hdfname + ".sec", sec.length() == 1 ? "0" + sec : sec);
224 
225     String min = Integer.toString(cal.get(Calendar.MINUTE));
226     setValue(hdfname + ".min", min.length() == 1 ? "0" + min : min);
227 
228     setValue(hdfname + ".24hour",
229         Integer.toString(cal.get(Calendar.HOUR_OF_DAY)));
230     // java.util.Calendar uses represents 12 o'clock as 0
231     setValue(hdfname + ".hour",
232         Integer.toString(
233             cal.get(Calendar.HOUR) == 0 ? 12 : cal.get(Calendar.HOUR)));
234     setValue(hdfname + ".am",
235         cal.get(Calendar.AM_PM) == Calendar.AM ? "1" : "0");
236     setValue(hdfname + ".mday",
237         Integer.toString(cal.get(Calendar.DAY_OF_MONTH)));
238     setValue(hdfname + ".mon",
239         Integer.toString(cal.get(Calendar.MONTH)+1));
240     setValue(hdfname + ".year",
241         Integer.toString(cal.get(Calendar.YEAR)));
242     setValue(hdfname + ".2yr",
243         Integer.toString(cal.get(Calendar.YEAR)).substring(2));
244 
245     // Java DAY_OF_WEEK puts Sunday .. Saturday as 1 .. 7 respectively
246     // See http://java.sun.com/j2se/1.5.0/docs/api/java/util/Calendar.html#DAY_OF_WEEK
247     // However, C and Python export Sun .. Sat as 0 .. 6, because
248     // POSIX localtime_r produces wday 0 .. 6.  So, adjust.
249     setValue(hdfname + ".wday",
250         Integer.toString(cal.get(Calendar.DAY_OF_WEEK) - 1));
251 
252     boolean tzNegative = timeZone.getRawOffset() < 0;
253     int tzAbsolute = java.lang.Math.abs(timeZone.getRawOffset()/1000);
254     String tzHour = Integer.toString(tzAbsolute/3600);
255     String tzMin = Integer.toString(tzAbsolute/60 - (tzAbsolute/3600)*60);
256     String tzString = (tzNegative ? "-" : "+")
257         + (tzHour.length() == 1 ? "0" + tzHour : tzHour)
258         + (tzMin.length() == 1 ? "0" + tzMin : tzMin);
259     setValue(hdfname + ".tzoffset", tzString);
260   }
261 
262   /** Export a date to a clearsilver tree using a specified timezone */
263   public void exportDate(String hdfname, String tz, int tt) {
264     if (hdfptr == 0) {
265       throw new NullPointerException("HDF is closed.");
266     }
267 
268     TimeZone timeZone = TimeZone.getTimeZone(tz);
269 
270     if (timeZone == null) {
271       throw new RuntimeException("Unknown timezone: " + tz);
272     }
273 
274     Date date = new Date((long)tt * 1000);
275 
276     exportDate(hdfname, timeZone, date);
277   }
278 
279   /** Retrieves the HDF object that is the root of the subtree at hdfpath, or
280    *  null if no object exists at that path. */
281   public JniHdf getObj(String hdfpath) {
282     if (hdfptr == 0) {
283       throw new NullPointerException("HDF is closed.");
284     }
285     long obj_ptr = _getObj(hdfptr, hdfpath);
286     if ( obj_ptr == 0 ) {
287       return null;
288     }
289     return newHdf(obj_ptr, this);
290   }
291 
292   /** Retrieves the HDF for the first child of the root of the subtree
293    *  at hdfpath, or null if no child exists of that path or if the
294    *  path doesn't exist. */
295   public JniHdf getChild(String hdfpath) {
296     if (hdfptr == 0) {
297       throw new NullPointerException("HDF is closed.");
298     }
299     long obj_ptr = _getChild(hdfptr, hdfpath);
300     if ( obj_ptr == 0 ) {
301       return null;
302     }
303     return newHdf(obj_ptr, this);
304   }
305 
306   /** Return the root of the tree where the current node lies.  If the
307    *  current node is the root, return this. */
308   public JniHdf getRootObj() {
309     return root != null ? root : this;
310   }
311 
312   public boolean belongsToSameRoot(HDF hdf) {
313     JniHdf jniHdf = cast(hdf);
314     return this.getRootObj() == jniHdf.getRootObj();
315   }
316 
317   /** Retrieves the HDF object that is the root of the subtree at
318    *  hdfpath, create the subtree if it doesn't exist */
319   public JniHdf getOrCreateObj(String hdfpath) {
320     if (hdfptr == 0) {
321       throw new NullPointerException("HDF is closed.");
322     }
323     long obj_ptr = _getObj(hdfptr, hdfpath);
324     if ( obj_ptr == 0 ) {
325       // Create a node
326       _setValue(hdfptr, hdfpath, "");
327       obj_ptr = _getObj( hdfptr, hdfpath );
328       if ( obj_ptr == 0 ) {
329         return null;
330       }
331     }
332     return newHdf(obj_ptr, this);
333   }
334 
335   /** Returns the name of this HDF node.   The root node has no name, so
336    *  calling this on the root node will return null. */
337   public String objName() {
338     if (hdfptr == 0) {
339       throw new NullPointerException("HDF is closed.");
340     }
341     return _objName(hdfptr);
342   }
343 
344   /** Returns the value of this HDF node, or null if this node has no value.
345    *  Every node in the tree can have a value, a child, and a next peer. */
346   public String objValue() {
347     if (hdfptr == 0) {
348       throw new NullPointerException("HDF is closed.");
349     }
350     return _objValue(hdfptr);
351   }
352 
353   /** Returns the child of this HDF node, or null if there is no child.
354    *  Use this in conjunction with objNext to walk the HDF tree.  Every node
355    *  in the tree can have a value, a child, and a next peer.
356    */
357   public JniHdf objChild() {
358     if (hdfptr == 0) {
359       throw new NullPointerException("HDF is closed.");
360     }
361     long child_ptr = _objChild(hdfptr);
362     if ( child_ptr == 0 ) {
363       return null;
364     }
365     return newHdf(child_ptr, this);
366   }
367 
368   /** Returns the next sibling of this HDF node, or null if there is no next
369    *  sibling.  Use this in conjunction with objChild to walk the HDF tree.
370    *  Every node in the tree can have a value, a child, and a next peer.
371    */
372   public JniHdf objNext() {
373     if (hdfptr == 0) {
374       throw new NullPointerException("HDF is closed.");
375     }
376     long next_ptr = _objNext(hdfptr);
377     if ( next_ptr == 0 ) {
378       return null;
379     }
380     return newHdf(next_ptr, this);
381   }
382 
383   public void copy(String hdfpath, HDF src) {
384     JniHdf source = cast(src);
385     if (hdfptr == 0 || source.hdfptr == 0) {
386       throw new NullPointerException("HDF is closed.");
387     }
388     _copy(hdfptr, hdfpath, source.hdfptr);
389   }
390 
391   /**
392    * Generates a string representing the content of the HDF tree rooted at
393    * this node.
394    */
395   public String dump() {
396     if (hdfptr == 0) {
397       throw new NullPointerException("HDF is closed.");
398     }
399     return _dump(hdfptr);
400   }
401 
402   private static native long _init();
403   private static native void _dealloc(long ptr);
404   private native boolean _readFile(long ptr, String filename, boolean use_cb)
405       throws IOException;
406   private static native boolean _writeFile(long ptr, String filename);
407   private static native boolean _readString(long ptr, String data);
408   private static native String _writeString(long ptr);
409   private static native int _getIntValue(long ptr, String hdfname,
410       int default_value);
411   private static native String _getValue(long ptr, String hdfname,
412       String default_value);
413   private static native void _setValue(long ptr, String hdfname,
414       String hdf_value);
415   private static native void _removeTree(long ptr, String hdfname);
416   private static native void _setSymLink(long ptr, String hdf_name_src,
417       String hdf_name_dest);
418   private static native long _getObj(long ptr, String hdfpath);
419   private static native long _getChild(long ptr, String hdfpath);
420   private static native long _objChild(long ptr);
421   private static native long _objNext(long ptr);
422   private static native String _objName(long ptr);
423   private static native String _objValue(long ptr);
424   private static native void _copy(long destptr, String hdfpath, long srcptr);
425 
426   private static native String _dump(long ptr);
427 }
428