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 com.sun.xml.xsom.XSComplexType;
026import com.sun.xml.xsom.XSElementDecl;
027import com.sun.xml.xsom.XSSchema;
028import com.sun.xml.xsom.XSSchemaSet;
029import com.sun.xml.xsom.parser.JAXPParser;
030import com.sun.xml.xsom.parser.XSOMParser;
031import org.xml.sax.ErrorHandler;
032import org.xml.sax.InputSource;
033import org.xml.sax.SAXException;
034import org.xml.sax.SAXParseException;
035
036import javax.xml.parsers.SAXParserFactory;
037import java.io.File;
038import java.io.Reader;
039import java.net.MalformedURLException;
040import java.util.ArrayList;
041import java.util.List;
042import java.util.logging.Level;
043import java.util.logging.Logger;
044
045/**
046 * Reads the record definition and creates as many {@link RecordDefinition}s as needed
047 */
048public class DefinitionLoader {
049
050  private final Logger log = Logger.getLogger(DefinitionLoader.class.getName());
051
052  private final Evaluators evaluators;
053  private final RecordDefinition recordDefinition;
054  private boolean loaded;
055
056  public DefinitionLoader() {
057    this.recordDefinition = new RecordDefinition();
058    this.evaluators = new Evaluators();
059    this.loaded = false;
060  }
061
062  /**
063   * Parses the input .xsd and creates as many {@link RecordDefinition}s as needed
064   *
065   * @param file the XML Schema file with the file definition
066   * @return the loaded record definition
067   */
068  public RecordDefinition load(File file) {
069    try {
070      return load(new InputSource(file.toURI().toURL().toExternalForm()));
071    } catch (MalformedURLException e) {
072      throw new RuntimeException(e);
073    }
074  }
075
076  /**
077   * Parses the input .xsd and creates as many {@link RecordDefinition}s as needed
078   *
079   * @param reader a reader that reads the XML Schema file with the file definition
080   * @return the loaded record definition
081   */
082  public RecordDefinition load(Reader reader) {
083    return load(new InputSource(reader));
084  }
085
086  /**
087   * Parses the input .xsd and creates as many {@link RecordDefinition}s as needed
088   *
089   * @param input an InputSource that reads the XML Schema file with the file definition
090   * @return the loaded record definition
091   */
092  public RecordDefinition load(InputSource input) {
093    if (loaded) {
094      throw new IllegalStateException("Already loaded");
095    }
096
097    log.log(Level.FINE, "Loading record definition");
098
099    XSSchemaSet schemas = loadSchemas(input);
100    XSSchema mainSchema = findMainSchema(schemas);
101    XSElementDecl mainElement = mainSchema.getElementDecl("main");
102
103    for (Evaluator<RecordDefinition, XSElementDecl> e : evaluators.mainElementEvaluators()) {
104      e.eval(recordDefinition, mainElement);
105    }
106
107    XSComplexType mainRecordType = mainElement.getType().asComplexType();
108    for (Evaluator<RecordDefinition, XSComplexType> e : evaluators.typeEvaluators()) {
109      e.eval(recordDefinition, mainRecordType);
110    }
111
112    mainRecordType.getContentType().visit(new Visitor(evaluators, mainSchema, schemas, recordDefinition));
113
114    this.loaded = true;
115
116    return recordDefinition;
117  }
118
119  private XSSchemaSet loadSchemas(InputSource input) {
120    XSOMParser parser = new XSOMParser(new JAXPParser(SAXParserFactory.newInstance()));
121    parser.setErrorHandler(new ErrorHandler() {
122
123      public void error(SAXParseException exception) {
124        throw new RuntimeException(exception);
125      }
126
127      public void fatalError(SAXParseException exception) {
128        throw new RuntimeException(exception);
129      }
130
131      public void warning(SAXParseException exception) {
132        throw new RuntimeException(exception);
133      }
134
135    });
136
137    try {
138      parser.parse(input);
139
140      return parser.getResult();
141    } catch (SAXException e) {
142      throw new RuntimeException(e);
143    }
144  }
145
146  private XSSchema findMainSchema(XSSchemaSet schemas) {
147    List<XSSchema> schemasWithMainElement = new ArrayList<>();
148    for (XSSchema schema : schemas.getSchemas()) {
149      XSElementDecl main = schema.getElementDecl("main");
150      if (main != null) {
151        schemasWithMainElement.add(schema);
152      }
153    }
154
155    if (schemasWithMainElement.isEmpty()) {
156      throw new IllegalArgumentException("No \"main\" element found");
157    }
158
159    if (schemasWithMainElement.size() != 1) {
160      throw new IllegalArgumentException("Multiple \"main\" elements found in your schemas");
161    }
162
163    return schemasWithMainElement.get(0);
164  }
165
166}