1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.pdfbox.pdmodel.common; 18 19 import java.io.IOException; 20 import java.lang.reflect.Constructor; 21 import java.util.ArrayList; 22 import java.util.Collections; 23 import java.util.HashMap; 24 import java.util.List; 25 import java.util.Map; 26 27 import org.apache.pdfbox.cos.COSArray; 28 import org.apache.pdfbox.cos.COSBase; 29 import org.apache.pdfbox.cos.COSDictionary; 30 import org.apache.pdfbox.cos.COSInteger; 31 import org.apache.pdfbox.cos.COSName; 32 33 /** 34 * This class represents a PDF Number tree. See the PDF Reference 1.7 section 35 * 7.9.7 for more details. 36 * 37 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>, 38 * <a href="igor.podolskiy@ievvwi.uni-stuttgart.de">Igor Podolskiy</a> 39 * @version $Revision: 1.4 $ 40 */ 41 public class PDNumberTreeNode implements COSObjectable 42 { 43 private COSDictionary node; 44 private Class valueType = null; 45 46 /** 47 * Constructor. 48 * 49 * @param valueClass The PD Model type of object that is the value. 50 */ 51 public PDNumberTreeNode( Class valueClass ) 52 { 53 node = new COSDictionary(); 54 valueType = valueClass; 55 } 56 57 /** 58 * Constructor. 59 * 60 * @param dict The dictionary that holds the name information. 61 * @param valueClass The PD Model type of object that is the value. 62 */ 63 public PDNumberTreeNode( COSDictionary dict, Class valueClass ) 64 { 65 node = dict; 66 valueType = valueClass; 67 } 68 69 /** 70 * Convert this standard java object to a COS object. 71 * 72 * @return The cos object that matches this Java object. 73 */ 74 public COSBase getCOSObject() 75 { 76 return node; 77 } 78 79 /** 80 * Convert this standard java object to a COS object. 81 * 82 * @return The cos object that matches this Java object. 83 */ 84 public COSDictionary getCOSDictionary() 85 { 86 return node; 87 } 88 89 /** 90 * Return the children of this node. This list will contain PDNumberTreeNode objects. 91 * 92 * @return The list of children or null if there are no children. 93 */ 94 public List getKids() 95 { 96 97 List retval = null; 98 COSArray kids = (COSArray)node.getDictionaryObject( COSName.KIDS ); 99 if( kids != null ) 100 { 101 List pdObjects = new ArrayList(); 102 for( int i=0; i<kids.size(); i++ ) 103 { 104 pdObjects.add( createChildNode( (COSDictionary)kids.getObject(i) ) ); 105 } 106 retval = new COSArrayList(pdObjects,kids); 107 } 108 109 return retval; 110 } 111 112 /** 113 * Set the children of this number tree. 114 * 115 * @param kids The children of this number tree. 116 */ 117 public void setKids( List kids ) 118 { 119 node.setItem( "Kids", COSArrayList.converterToCOSArray( kids ) ); 120 } 121 122 /** 123 * Returns the value corresponding to an index in the number tree. 124 * 125 * @param index The index in the number tree. 126 * 127 * @return The value corresponding to the index. 128 * 129 * @throws IOException If there is a problem creating the values. 130 */ 131 public Object getValue( Integer index ) throws IOException 132 { 133 Object retval = null; 134 Map names = getNumbers(); 135 if( names != null ) 136 { 137 retval = names.get( index ); 138 } 139 else 140 { 141 List kids = getKids(); 142 for( int i=0; i<kids.size() && retval == null; i++ ) 143 { 144 PDNumberTreeNode childNode = (PDNumberTreeNode)kids.get( i ); 145 if( childNode.getLowerLimit().compareTo( index ) <= 0 && 146 childNode.getUpperLimit().compareTo( index ) >= 0 ) 147 { 148 retval = childNode.getValue( index ); 149 } 150 } 151 } 152 return retval; 153 } 154 155 156 /** 157 * This will return a map of numbers. The key will be a java.lang.Integer, the value will 158 * depend on where this class is being used. 159 * 160 * @return A map of COS objects. 161 * 162 * @throws IOException If there is a problem creating the values. 163 */ 164 public Map getNumbers() throws IOException 165 { 166 Map indices = null; 167 COSArray namesArray = (COSArray)node.getDictionaryObject( COSName.NUMS ); 168 if( namesArray != null ) 169 { 170 indices = new HashMap(); 171 for( int i=0; i<namesArray.size(); i+=2 ) 172 { 173 COSInteger key = (COSInteger)namesArray.getObject(i); 174 COSBase cosValue = namesArray.getObject( i+1 ); 175 Object pdValue = convertCOSToPD( cosValue ); 176 177 indices.put( Integer.valueOf(key.intValue()), pdValue ); 178 } 179 indices = Collections.unmodifiableMap(indices); 180 } 181 182 return indices; 183 } 184 185 /** 186 * Method to convert the COS value in the name tree to the PD Model object. The 187 * default implementation will simply use reflection to create the correct object 188 * type. Subclasses can do whatever they want. 189 * 190 * @param base The COS object to convert. 191 * @return The converted PD Model object. 192 * @throws IOException If there is an error during creation. 193 */ 194 protected Object convertCOSToPD( COSBase base ) throws IOException 195 { 196 Object retval = null; 197 try 198 { 199 Constructor ctor = valueType.getConstructor( new Class[] { base.getClass() } ); 200 retval = ctor.newInstance( new Object[] { base } ); 201 } 202 catch( Throwable t ) 203 { 204 throw new IOException( "Error while trying to create value in number tree:" + t.getMessage()); 205 206 } 207 return retval; 208 } 209 210 /** 211 * Create a child node object. 212 * 213 * @param dic The dictionary for the child node object to refer to. 214 * @return The new child node object. 215 */ 216 protected PDNumberTreeNode createChildNode( COSDictionary dic ) 217 { 218 return new PDNumberTreeNode(dic,valueType); 219 } 220 221 /** 222 * Set the names of for this node. The keys should be java.lang.String and the 223 * values must be a COSObjectable. This method will set the appropriate upper and lower 224 * limits based on the keys in the map. 225 * 226 * @param numbers The map of names to objects. 227 */ 228 public void setNumbers( Map numbers ) 229 { 230 if( numbers == null ) 231 { 232 node.setItem( COSName.NUMS, (COSObjectable)null ); 233 node.setItem( "Limits", (COSObjectable)null); 234 } 235 else 236 { 237 List keys = new ArrayList( numbers.keySet() ); 238 Collections.sort( keys ); 239 COSArray array = new COSArray(); 240 for( int i=0; i<keys.size(); i++ ) 241 { 242 Integer key = (Integer)keys.get(i); 243 array.add( COSInteger.get( key ) ); 244 COSObjectable obj = (COSObjectable)numbers.get( key ); 245 array.add( obj ); 246 } 247 Integer lower = null; 248 Integer upper = null; 249 if( keys.size() > 0 ) 250 { 251 lower = (Integer)keys.get( 0 ); 252 upper = (Integer)keys.get( keys.size()-1 ); 253 } 254 setUpperLimit( upper ); 255 setLowerLimit( lower ); 256 node.setItem( "Names", array ); 257 } 258 } 259 260 /** 261 * Get the highest value for a key in the name map. 262 * 263 * @return The highest value for a key in the map. 264 */ 265 public Integer getUpperLimit() 266 { 267 Integer retval = null; 268 COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS ); 269 if( arr != null ) 270 { 271 retval = Integer.valueOf(arr.getInt( 1 )); 272 } 273 return retval; 274 } 275 276 /** 277 * Set the highest value for the key in the map. 278 * 279 * @param upper The new highest value for a key in the map. 280 */ 281 private void setUpperLimit( Integer upper ) 282 { 283 COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS ); 284 if( arr == null ) 285 { 286 arr = new COSArray(); 287 arr.add( null ); 288 arr.add( null ); 289 } 290 arr.setInt( 1, upper.intValue() ); 291 } 292 293 /** 294 * Get the lowest value for a key in the name map. 295 * 296 * @return The lowest value for a key in the map. 297 */ 298 public Integer getLowerLimit() 299 { 300 Integer retval = null; 301 COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS ); 302 if( arr != null ) 303 { 304 retval = Integer.valueOf(arr.getInt( 0 )); 305 } 306 return retval; 307 } 308 309 /** 310 * Set the lowest value for the key in the map. 311 * 312 * @param lower The new lowest value for a key in the map. 313 */ 314 private void setLowerLimit( Integer lower ) 315 { 316 COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS ); 317 if( arr == null ) 318 { 319 arr = new COSArray(); 320 arr.add( null ); 321 arr.add( null ); 322 } 323 arr.setInt( 0, lower.intValue() ); 324 } 325 }