1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 import java.io.OutputStream;
25 import java.io.InputStream;
26 import java.io.DataOutputStream;
27 import java.io.DataInputStream;
28 import java.io.IOException;
29 import java.io.UnsupportedEncodingException;
30 
31 /**
32  *  <p>This class contains data for a single Palm database for use during
33  *  a conversion process.</p>
34  *
35  *  <p>It contains zero or more <code>Record</code> objects stored in an
36  *  array. The index of the <code>Record</code> object in the array is
37  *  the record id or number for that specific <code>Record</code> object.
38  *  Note that this class does not check for maximum number of records
39  *  allowable in an actual pdb.</p>
40  *
41  *  <p>This class also contains the pdb name associated with the Palm database
42  *  it represents. A pdb name consists of 32 bytes of a certain encoding
43  *  (extended ASCII in this case).</p>
44  *
45  *  <p>The non default constructors take in a name parameter which may not
46  *  be the exact pdb name to be used.  The name parameter in
47  *  <code>String</code> or <code>byte[]</code> are converted to an exact
48  *  <code>NAME_LENGTH</code> byte array.  If the length of the name is less
49  *  than <code>NAME_LENGTH</code>, it is padded with '\0' characters.  If it
50  *  is more, it gets truncated.  The last character in the resulting byte
51  *  array is always a '\0' character.  The resulting byte array is stored in
52  *  <code>bName</code>, and a corresponding String object <code>sName</code>
53  *  that contains characters without the '\0' characters.</p>
54  *
55  *  <p>The {@link #write write} method is called within the
56  *  {@link zensync.util.palm.PalmDBSet#write PalmDBSet.write} method
57  *  for writing out its data to the <code>OutputStream</code> object.</p>
58  *
59  *  <p>The {@link #read read} method is called within the
60  *  {@link zensync.util.palm.PalmDBSet#read PalmDBSet.read} method
61  *  for reading in its data from the <code>InputStream</code> object.</p>
62  *
63  *  @author    Akhil Arora, Herbie Ong
64  *  @see    PalmDBSet
65  *  @see    Record
66  */
67 
68 public final class PalmDB {
69 
70     /** number of bytes for the name field in the pdb */
71     public final static int NAME_LENGTH = 32;
72 
73     /** list of Record objects */
74     private Record[] records;
75 
76     /** pdb name in bytes */
77     private byte[] bName = null;
78 
79     /** pdb name in String */
80     private String sName = null;
81 
82 
83     /**
84      *  Default constructor for use after a read().
85      */
86 
PalmDB()87     public PalmDB() {
88 
89         records = new Record[0];
90     }
91 
92     /**
93      *  Constructor to create object with Record objects.
94      *  recs.length can be zero for an empty pdb.
95      *
96      *  @param   name    suggested pdb name in String
97      *  @param   recs    array of Record objects
98      *  @throws  NullPointerException    if recs is null
99      */
100 
PalmDB(String name, Record[] recs)101     public PalmDB(String name, Record[] recs)
102         throws UnsupportedEncodingException {
103 
104         this(name.getBytes(PDBUtil.ENCODING), recs);
105     }
106 
107     /**
108      *  Constructor to create object with Record objects.
109      *  recs.length can be zero for an empty pdb.
110      *
111      *  @param   name    suggested pdb name in byte array
112      *  @param   recs    array of Record objects
113      *  @throws  NullPointerException    if recs is null
114      */
115 
PalmDB(byte[] name, Record[] recs)116     public PalmDB(byte[] name, Record[] recs)
117         throws UnsupportedEncodingException {
118 
119         store(name);
120 
121         records = new Record[recs.length];
122         System.arraycopy(recs, 0, records, 0, recs.length);
123     }
124 
125     /**
126      *  This private method is mainly used by the constructors above.
127      *  to store bytes into name and also create a String representation.
128      *  and also by the read method.
129      *
130      *  TODO: Note that this method assumes that the byte array parameter
131      *  contains one character per byte, else it would truncate
132      *  improperly.
133      *
134      *  @param   bytes    pdb name in byte array
135      *  @throws   UnsupportedEncodingException    if ENCODING is not supported
136      */
137 
store(byte[] bytes)138     private void store(byte[] bytes) throws UnsupportedEncodingException {
139 
140         // note that this will initialize all bytes in name to 0.
141         bName = new byte[NAME_LENGTH];
142 
143         // determine minimum length to copy over from bytes to bName.
144         // Note that the last byte in bName has to be '\0'.
145 
146         int lastIndex = NAME_LENGTH - 1;
147 
148         int len = (bytes.length < lastIndex)? bytes.length: lastIndex;
149 
150         int i;
151 
152         for (i = 0; i < len; i++) {
153 
154             if (bytes[i] == 0) {
155                 break;
156             }
157 
158             bName[i] = bytes[i];
159         }
160 
161         // set sName, no need to include the '\0' character.
162         sName = new String(bName, 0, i, PDBUtil.ENCODING);
163     }
164 
165     /**
166      *  Return the number of records contained in this
167      *  pdb PalmDB object.
168      *
169      *  @return  int    number of Record objects
170      */
171 
getRecordCount()172     public int getRecordCount() {
173 
174         return records.length;
175     }
176 
177     /**
178      *  Return the specific Record object associated
179      *  with the record number.
180      *
181      *  @param   index    record index number
182      *  @return  Record   the Record object in the specified index
183      *  @throws  ArrayIndexOutOfBoundsException    if index is out of bounds
184      */
185 
getRecord(int index)186     public Record getRecord(int index) {
187 
188         return records[index];
189     }
190 
191     /**
192      *  Return the list of Record objects
193      *
194      *  @return  Record[]   the list of Record objects
195      */
196 
getRecords()197     public Record[] getRecords() {
198 
199         return records;
200     }
201 
202     /**
203      *  Return the PDBName associated with this object in String
204      *
205      *  @return  String    pdb name in String
206      */
207 
getPDBNameString()208     public String getPDBNameString() {
209 
210         return sName;
211     }
212 
213     /**
214      *  Return the PDBName associated with this object
215      *  in byte array of exact length of 32 bytes.
216      *
217      *  @return  byte[]    pdb name in byte[] of length 32.
218      */
219 
getPDBNameBytes()220     public byte[] getPDBNameBytes() {
221 
222         return bName;
223     }
224 
225     /**
226      *  Write out the number of records followed by what
227      *  will be written out by each Record object.
228      *
229      *  @param   os    the stream to write the object to
230      *  @throws  IOException    if any I/O error occurs
231      */
232 
write(OutputStream os)233     public void write(OutputStream os) throws IOException {
234 
235         DataOutputStream out = new DataOutputStream(os);
236 
237         // write out pdb name
238         out.write(bName);
239 
240         // write out 2 bytes for number of records
241         out.writeShort(records.length);
242 
243         // let each Record object write out its own info.
244         for (int i = 0; i < records.length; i++)
245             records[i].write(out);
246     }
247 
248     /**
249      *  Read the necessary data to create a pdb from
250      *  the input stream.
251      *
252      *  @param   is    the stream to read data from in order
253      *                 to restore the object
254      *  @throws  IOException    if any I/O error occurs
255      */
256 
read(InputStream is)257     public void read(InputStream is) throws IOException {
258 
259         DataInputStream in = new DataInputStream(is);
260 
261         // read in the pdb name.
262         byte[] bytes = new byte[NAME_LENGTH];
263         in.readFully(bytes);
264         store(bytes);
265 
266         // read in number of records
267         int nrec = in.readUnsignedShort();
268         records = new Record[nrec];
269 
270         // read in the Record infos
271         for (int i = 0; i < nrec; i++) {
272 
273             records[i] = new Record();
274             records[i].read(in);
275         }
276     }
277 
278     /**
279      *  Override equals method of Object.
280      *
281      *  2 PalmDB objects are equal if they contain the same information,
282      *  i.e. pdb name and records.
283      *
284      *  This is used primarily for testing purposes only for now.
285      *
286      *  @param   obj    a PalmDB object to compare with
287      *  @return   boolean    true if obj is equal to this, else false.
288      */
289 
equals(Object obj)290     public boolean equals(Object obj) {
291 
292         boolean bool = false;
293 
294         if (obj instanceof PalmDB) {
295 
296             PalmDB pdb = (PalmDB) obj;
297 
298             checkLabel: {
299 
300                 // compare sName
301 
302                 if (!sName.equals(pdb.sName)) {
303 
304                     break checkLabel;
305                 }
306 
307                 // compare bName
308 
309                 if (bName.length != pdb.bName.length) {
310 
311                     break checkLabel;
312                 }
313 
314                 for (int i = 0; i < bName.length; i++) {
315 
316                     if (bName[i] != pdb.bName[i]) {
317 
318                         break checkLabel;
319                     }
320                 }
321 
322                 // compare each Record
323 
324                 if (records.length != pdb.records.length) {
325 
326                     break checkLabel;
327                 }
328 
329                 for (int i = 0; i < records.length; i++) {
330 
331                     if (!records[i].equals(pdb.records[i])) {
332 
333                         break checkLabel;
334                     }
335                 }
336 
337                 // all checks done
338                 bool = true;
339             }
340         }
341 
342         return bool;
343     }
344 }
345