001/* 002 * JRecordBind, fixed-length file (un)marshaller 003 * Copyright 2019, Federico Fissore, and individual contributors. See 004 * AUTHORS.txt in the distribution for a full listing of individual 005 * contributors. 006 * 007 * This is free software; you can redistribute it and/or modify it 008 * under the terms of the GNU Lesser General Public License as 009 * published by the Free Software Foundation; either version 2.1 of 010 * the License, or (at your option) any later version. 011 * 012 * This software is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this software; if not, write to the Free 019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 021 */ 022 023package org.fissore.jrecordbind; 024 025import org.fissore.jrecordbind.padders.SpaceRightPadder; 026 027import java.util.ArrayList; 028import java.util.List; 029 030/*** 031 * The definition of the record bean. This bean rapresentation of 032 * the XML Schema fixed-length file definition (.xsd) 033 */ 034public class RecordDefinition { 035 036 /** 037 * A single "element" in the fixed-length file definition 038 */ 039 public static class Property { 040 041 private String converter; 042 private String fixedValue; 043 private int length; 044 private final String name; 045 private String padder; 046 private int row; 047 private String type; 048 private final EnumPropertyHelper enumPropertyHelper; 049 050 /** 051 * Creates a new Property 052 * 053 * @param name the name of the property 054 */ 055 public Property(String name) { 056 this.name = name; 057 this.enumPropertyHelper = new EnumPropertyHelper(this); 058 } 059 060 /** 061 * The fully qualified class name of the converter used to marshall and 062 * unmarshall this property 063 * 064 * @return a fully qualified class name 065 */ 066 public String getConverter() { 067 return converter; 068 } 069 070 /** 071 * The fixed value of this property, usually used to identify different 072 * lines in a single file 073 * 074 * @return the value 075 */ 076 public String getFixedValue() { 077 return fixedValue; 078 } 079 080 /** 081 * The length of the property 082 * 083 * @return the length of the property 084 */ 085 public int getLength() { 086 return length; 087 } 088 089 /** 090 * The name of the property 091 * 092 * @return the name of the property 093 */ 094 public String getName() { 095 return name; 096 } 097 098 /** 099 * The padder used to pad this property value. If missing (<code>null</code> 100 * ) the default padder will be used 101 * 102 * @return a fully qualified class name or <code>null</code> if none specified 103 */ 104 public String getPadder() { 105 return padder; 106 } 107 108 /** 109 * The row this property is at. If this is the first property at 110 * row 2, every subsequent property must be at least at row 2. Consistency 111 * is up to the definition writer (developer) 112 * 113 * @return the row 114 */ 115 public int getRow() { 116 return row; 117 } 118 119 /** 120 * The class name of the type of this property. Internally supported type 121 * names are not fully qualified (String, Integer...) 122 * 123 * @return the class name of this property type 124 */ 125 public String getType() { 126 return type; 127 } 128 129 public void setConverter(String converter) { 130 this.converter = converter; 131 } 132 133 public void setFixedValue(String value) { 134 this.fixedValue = value; 135 } 136 137 public void setLength(int end) { 138 this.length = end; 139 } 140 141 public void setPadder(String padder) { 142 this.padder = padder; 143 } 144 145 public void setRow(int row) { 146 this.row = row; 147 } 148 149 public void setType(String type) { 150 this.type = type; 151 } 152 153 public boolean isEnum() { 154 return enumPropertyHelper.isEnum(); 155 } 156 157 } 158 159 private boolean choice; 160 private String className; 161 private String defaultPadder; 162 private int length; 163 private String lineSeparator; 164 private int maxOccurs; 165 private int minOccurs; 166 private final RecordDefinition parent; 167 private String printableLineSeparator; 168 private final List<Property> properties; 169 private String propertyDelimiter; 170 private String propertyPattern; 171 private String setterName; 172 private final List<RecordDefinition> subRecords; 173 174 /** 175 * Creates a new instance, without a parent (aka: the main definition) 176 */ 177 public RecordDefinition() { 178 this(null); 179 } 180 181 /** 182 * Creates a new instance, with the given parent definition. 183 * 184 * @param parent the parent record definition 185 */ 186 public RecordDefinition(RecordDefinition parent) { 187 this.properties = new ArrayList<>(); 188 this.propertyDelimiter = ""; 189 this.subRecords = new ArrayList<>(); 190 this.parent = parent; 191 this.defaultPadder = SpaceRightPadder.class.getName(); 192 } 193 194 /** 195 * The fully qualified class name described by this definition 196 * 197 * @return a fully qualified class name 198 */ 199 public String getClassName() { 200 return className; 201 } 202 203 public String getDefaultPadder() { 204 if (hasParent()) { 205 return parent.getDefaultPadder(); 206 } 207 return defaultPadder; 208 } 209 210 /** 211 * The length of the fixed-length file 212 * 213 * @return the length 214 */ 215 public int getLength() { 216 if (hasParent()) { 217 return parent.getLength(); 218 } 219 return length; 220 } 221 222 public String getLineSeparator() { 223 if (hasParent()) { 224 return parent.getLineSeparator(); 225 } 226 return lineSeparator; 227 } 228 229 /** 230 * How many times this definition can occur in the fixed-length file? Main 231 * definition can occur 1 time only, subdefinitions can vary 232 * 233 * @return an int 234 */ 235 public int getMaxOccurs() { 236 return maxOccurs; 237 } 238 239 /** 240 * How many times this definition must occur in the fixed-length file? Main 241 * definition must occur 1 time, subdefinitions can vary 242 * 243 * @return an int 244 */ 245 public int getMinOccurs() { 246 return minOccurs; 247 } 248 249 public RecordDefinition getParent() { 250 return parent; 251 } 252 253 public String getPrintableLineSeparator() { 254 if (hasParent()) { 255 return parent.getPrintableLineSeparator(); 256 } 257 return printableLineSeparator; 258 } 259 260 /** 261 * The list of {@link Property properties} contained by this definition 262 * 263 * @return the list of {@link Property properties} 264 */ 265 public List<Property> getProperties() { 266 return properties; 267 } 268 269 /** 270 * The delimiter used in the fixed-length file 271 * 272 * @return the delimiter 273 */ 274 public String getPropertyDelimiter() { 275 if (hasParent()) { 276 return parent.getPropertyDelimiter(); 277 } 278 return propertyDelimiter; 279 } 280 281 /** 282 * Tells if properties of this record are delimited 283 * 284 * @return True if properties of the record are delimited, false if the record is fixed-length 285 */ 286 public boolean isDelimited() { 287 return (!"".equals(getPropertyDelimiter())); 288 } 289 290 /** 291 * The name of the property used to set records from this definition in the 292 * parent (container) definition. E.G.: the "name" attribute in "element" like 293 * the following<br> 294 * <code><xs:element name="child" type="RowChildRecord"/></code> 295 * 296 * @return a property name 297 */ 298 public String getSetterName() { 299 return setterName; 300 } 301 302 /** 303 * The sub definitions contained by this definition (hierarchy based files) 304 * 305 * @return the list of sub definitions 306 */ 307 public List<RecordDefinition> getSubRecords() { 308 return subRecords; 309 } 310 311 /** 312 * The regex that matches a property 313 * (i.e. a string that does not contain a delimiter) 314 * 315 * @return the regex that matches a delimited property 316 */ 317 public String getPropertyPattern() { 318 if (hasParent()) { 319 return parent.getPropertyPattern(); 320 } 321 return propertyPattern; 322 } 323 324 @Override 325 public int hashCode() { 326 return getClassName().hashCode(); 327 } 328 329 public boolean hasParent() { 330 return parent != null; 331 } 332 333 public boolean isChoice() { 334 return choice; 335 } 336 337 public void setChoice(boolean choice) { 338 this.choice = choice; 339 } 340 341 public void setClassName(String fullyQualifiedClassName) { 342 this.className = fullyQualifiedClassName; 343 } 344 345 public void setClassName(String className, String packageName) { 346 this.className = packageName + "." + className; 347 } 348 349 public void setDefaultPadder(String padder) { 350 if (padder == null) { 351 return; 352 } 353 this.defaultPadder = padder; 354 } 355 356 public void setLength(int length) { 357 this.length = length; 358 } 359 360 public void setLineSeparator(String separator) { 361 this.lineSeparator = separator; 362 StringBuilder sb = new StringBuilder(); 363 for (char c : this.lineSeparator.toCharArray()) { 364 if (!Character.isISOControl(c)) { 365 sb.append(c); 366 } 367 } 368 this.printableLineSeparator = sb.toString(); 369 } 370 371 public void setMaxOccurs(int maxOccurs) { 372 this.maxOccurs = maxOccurs; 373 } 374 375 public void setMinOccurs(int minOccurs) { 376 this.minOccurs = minOccurs; 377 } 378 379 public void setPropertyDelimiter(String delimiter) { 380 if (delimiter == null) { 381 return; 382 } 383 this.propertyDelimiter = delimiter; 384 this.propertyPattern = generatePropertyPattern(delimiter); 385 } 386 387 public void setSetterName(String setterName) { 388 this.setterName = setterName; 389 } 390 391 private String generatePropertyPattern(String delimiter) { 392 if (delimiter.equals("")) { 393 return ("."); 394 } else if (delimiter.length() == 1) { 395 return ("[^\\Q" + delimiter + "\\E\\n]"); 396 } else { 397 return "[^\\Q" + delimiter.substring(0, 1) + "\\E\\n]|(?:\\Q" + delimiter.substring(0, 1) + "\\E(?!\\Q" + delimiter.substring(1) + "\\E))"; 398 } 399 } 400 401}