/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2013, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.data.mapinfo.mif; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.data.FeatureReader; import org.geotoolkit.data.FeatureStoreRuntimeException; import org.apache.sis.util.ObjectConverters; import org.apache.sis.util.CharSequences; import org.geotoolkit.nio.IOUtilities; import java.io.IOException; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Scanner; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import org.apache.sis.feature.FeatureExt; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.UnconvertibleObjectException; import org.apache.sis.util.logging.Logging; import org.opengis.feature.AttributeType; import org.opengis.feature.Feature; import org.opengis.feature.FeatureType; import org.opengis.feature.PropertyType; /** * MIF reader which is designed to browse data AND ONLY data, it's to say geometry data from MIF file, and all data from * MID file. * * @author Alexis Manin (Geomatys) * @date : 22/02/13 */ public class MIFFeatureReader implements FeatureReader { private final static Logger LOGGER = Logging.getLogger("org.geotoolkit.data.mapinfo.mif"); private static final Pattern GEOMETRY_ID_PATTERN; static { final StringBuilder patternBuilder = new StringBuilder(); final MIFUtils.GeometryType[] types = MIFUtils.GeometryType.values(); patternBuilder.append(types[0].name()); for(int i = 1 ; i < types.length; i++) { patternBuilder.append('|').append(types[i].name()); } GEOMETRY_ID_PATTERN = Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE); } /** * MIF/MID input connections. */ private InputStream mifStream = null; private InputStream midStream = null; /** * MIF/MID file readers. */ private Scanner mifScanner = null; private Scanner midScanner = null; /** * Counters : feature counter (Mid and mif lines are not equal for the same feature). */ int mifCounter = 0; int midCounter = 0; /** * booleans to check if we just read mid file (feature type doesn't contain any geometry) or just MIF file * (geometries only), or both. */ boolean readMid = false; boolean readMif = false; final MIFManager master; final FeatureType readType; final PropertyType[] baseTypeAtts; final MIFUtils.GeometryType geometryType; final String geometryId; final Pattern geometryPattern; public MIFFeatureReader(MIFManager parent, String typeName) throws DataStoreException { ArgumentChecks.ensureNonNull("Parent reader", parent); master = parent; readType = master.getType(typeName); if(readType.equals(master.getBaseType()) || readType.getSuperTypes().contains(master.getBaseType())) { readMid = true; } if(FeatureExt.hasAGeometry(readType)) { readMif = true; geometryType = MIFUtils.identifyFeature(readType); geometryId = geometryType.name(); geometryPattern = Pattern.compile(geometryId, Pattern.CASE_INSENSITIVE); } else { geometryType = null; geometryId = null; geometryPattern = null; } baseTypeAtts = master.getBaseType().getProperties(true).toArray(new PropertyType[0]); } /** * {@inheritDoc} */ @Override public FeatureType getFeatureType() { return readType; } /** * {@inheritDoc} */ @Override public Feature next() throws FeatureStoreRuntimeException { Feature resFeature = null; try { checkScanners(); resFeature = readType.newInstance(); // We check the MIF file first, because it will define the feature count to reach the next good typed data. if(readMif) { String currentPattern; while(mifScanner.hasNextLine()) { currentPattern = mifScanner.findInLine(GEOMETRY_ID_PATTERN); if(geometryId.equalsIgnoreCase(currentPattern)) { //geometryType.readGeometry(mifScanner, resFeature, master.getTransform()); try{ geometryType.readGeometry(mifScanner, resFeature, master.getTransform()); }catch(Exception ex){ LOGGER.log(Level.WARNING,ex.getLocalizedMessage(),ex); } break; // We must check if we're on a Geometry naming line to increment the counter of past geometries. } else if(currentPattern != null) { mifCounter++; } mifScanner.nextLine(); } } if(readMid) { //parse MID line. while(midCounter < mifCounter) { midScanner.nextLine(); midCounter++; } final String line = midScanner.nextLine(); final CharSequence[] split = CharSequences.split(line, master.mifDelimiter); for (int i = 0; i < split.length; i++) { //AttributeType att = baseType.getType(i); AttributeType att = null; try{ att = (AttributeType)baseTypeAtts[i]; }catch(Exception ex){ LOGGER.finer(ex.getMessage()); } if(att == null) continue; Object value = null; try{ if (split[i].length() != 0) { // We don't use geotoolkit to parse date, because we have to use a specific date pattern. if(Date.class.isAssignableFrom(att.getValueClass())) { SimpleDateFormat format = new SimpleDateFormat(); if(split[i].length() > 14) { format.applyPattern("yyyyMMddHHmmss.SSS"); } else if(split[i].length() == 14) { format.applyPattern("yyyyMMddHHmmss"); } else { format.applyPattern("yyyyMMdd"); } value = format.parse(split[i].toString()); } else try { value = ObjectConverters.convert(split[i], att.getValueClass()); } catch (UnconvertibleObjectException e) { Logging.recoverableException(LOGGER, MIFFeatureReader.class, "next", e); value = null; // TODO - do we really want to ignore the problem? } } resFeature.setPropertyValue(att.getName().toString(),value); }catch(Exception ex){ LOGGER.finer(ex.getMessage()); } } midCounter++; } if(readMif) { mifCounter++; } } catch (Exception ex) { throw new FeatureStoreRuntimeException("Can't reach next feature with type name " + readType.getName().tip().toString(), ex); } return resFeature; } /** * {@inheritDoc} */ @Override public boolean hasNext() throws FeatureStoreRuntimeException { boolean midNext = false; boolean mifNext = false; try { try { checkScanners(); } catch (IOException e) { // If we can't access source files, maybe we're in creation mode, so we just say we can't find next. return false; } if (readMif) { // Check the MapInfo geometry typename to see if there's some next in the file. while(mifScanner.hasNext()) { if (mifScanner.hasNext(geometryPattern)) { mifNext = true; break; } else { // We must check if we're on a Geometry naming line to increment the counter of past geometries. if(mifScanner.hasNext(GEOMETRY_ID_PATTERN)) { mifCounter++; } } mifScanner.next(); } } // Once we know the number of the next geometry data, we can check if we can go as far in the mid file. if (readMid) { for( ; midCounter < mifCounter ; midCounter++) { if(midScanner.hasNextLine()) { midScanner.nextLine(); } else { break; } } midNext = midScanner.hasNextLine(); } } catch (Exception ex) { throw new FeatureStoreRuntimeException(ex); } if (readMid && !readMif) { return midNext; } else if (readMif && !readMid) { return mifNext; } else return (midNext && mifNext); } /** * {@inheritDoc} */ @Override public void close() { mifCounter = 0; midCounter = 0; if (mifScanner != null) { mifScanner.close(); mifScanner = null; } if (midScanner != null) { midScanner.close(); midScanner = null; } try { if (mifStream != null) { mifStream.close(); mifStream = null; } if (midStream != null) { midStream.close(); midStream = null; } } catch (IOException e) { LOGGER.log(Level.WARNING, "Input connections to MIF/MID files can't be closed.", e); } } /** * {@inheritDoc} */ @Override public void remove() { throw new UnsupportedOperationException("MIF feature iterator is for reading only."); } /** * Check if we have an open access to mif/mid files. If not, try to get one. * * @throws DataStoreException If we're unable to access files. */ private void checkScanners() throws DataStoreException, IOException { if(readMif) { if(mifStream == null) { mifStream = IOUtilities.open(master.getMIFPath()); } if(mifScanner == null) { mifScanner = new Scanner(mifStream, MIFUtils.DEFAULT_CHARSET); repositionMIFReader(); } } if(readMid) { if(midStream == null) { midStream = IOUtilities.open(master.getMIDPath()); } if(midScanner == null) { midScanner = new Scanner(midStream, MIFUtils.DEFAULT_CHARSET); // Reposition the scanner. int midPosition = 0; while (midPosition < midCounter) { midScanner.nextLine(); midPosition++; } } } } /** * Check the current feature counter, and move the mif scanner according to that counter. It's useful if MIF scanner * have been reset but not iterator position. * * WARNING : YOU <b>MUST NOT</b> USE THIS FUNCTION IF SCANNERS ARE NOT EARLY PLACED IN THE INPUT FILE. */ private void repositionMIFReader() throws DataStoreException { // Check for column pattern while (mifScanner.hasNextLine()) { if (mifScanner.hasNext("(?i)\\s*"+MIFUtils.HeaderCategory.COLUMNS.name())) { mifScanner.next(); if (mifScanner.hasNextShort()) { short mifColumnsCount = mifScanner.nextShort(); for (int i = 0 ; i < mifColumnsCount ; i++) { mifScanner.nextLine(); } break; } else { throw new DataStoreException("MIF Columns has no attribute count specified."); } } mifScanner.nextLine(); } // Go to the first feature. while (mifScanner.hasNextLine()) { if (mifScanner.hasNext(GEOMETRY_ID_PATTERN)) { break; } mifScanner.nextLine(); } //Browse file until we're well placed. int mifPosition = 0; while (mifPosition < mifCounter) { while (mifScanner.hasNextLine()) { mifScanner.nextLine(); if (mifScanner.hasNext(GEOMETRY_ID_PATTERN)) { mifPosition++; break; } } } } }