Java tutorial
/* * Copyright 2015 Patrik Karlsson. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package se.trixon.toolbox.photokml; import se.trixon.almond.Scaler; import com.drew.imaging.ImageMetadataReader; import com.drew.imaging.ImageProcessingException; import com.drew.lang.GeoLocation; import com.drew.metadata.Metadata; import com.drew.metadata.exif.ExifSubIFDDirectory; import com.drew.metadata.exif.GpsDescriptor; import com.drew.metadata.exif.GpsDirectory; import de.micromata.opengis.kml.v_2_2_0.Folder; import de.micromata.opengis.kml.v_2_2_0.Kml; import java.awt.Dimension; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.attribute.BasicFileAttributes; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.openide.awt.StatusDisplayer; import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; import se.trixon.almond.GraphicsHelper; import se.trixon.almond.dialogs.Message; import se.trixon.almond.dictionary.Dict; import se.trixon.toolbox.core.Toolbox; /** * * @author Patrik Karlsson <patrik@trixon.se> */ public class Operation { private final ResourceBundle mBundle; private final File mDestinationFile; private final List<Exception> mExceptions = new ArrayList<>(); private final List<File> mFiles = new ArrayList<>(); private boolean mInterrupted = false; private final OperationListener mListener; private final Options mOptions = Options.INSTANCE; private ProgressHandle mProgressHandle; private final RequestProcessor mRequestProcessor = new RequestProcessor("interruptible tasks", 1, true); private final File[] mSourceFiles; private final PathMatcher mSourcePattern; private final boolean mValidSourceString; public Operation(OperationListener operationListener, File destination) { mListener = operationListener; mDestinationFile = destination; mBundle = NbBundle.getBundle(PhotoKmlTopComponent.class); mValidSourceString = mOptions.getSourcePaths().length() > 0; String[] sourcePathsAsStrings = mOptions.getSourcePaths().split(SystemUtils.PATH_SEPARATOR); mSourceFiles = new File[sourcePathsAsStrings.length]; for (int i = 0; i < sourcePathsAsStrings.length; i++) { if (SystemUtils.IS_OS_WINDOWS) { sourcePathsAsStrings[i] = StringUtils.trim(sourcePathsAsStrings[i]); } mSourceFiles[i] = new File(sourcePathsAsStrings[i]); } mSourcePattern = FileSystems.getDefault().getPathMatcher("glob:" + mOptions.getSourcePattern()); } public void start() { if (!mValidSourceString) { return; } mListener.onOperationStarted(); OperationRunnable runnable = new OperationRunnable(); final RequestProcessor.Task task = mRequestProcessor.create(runnable); mProgressHandle = ProgressHandleFactory.createHandle(mBundle.getString("Tool-Name"), task); task.addTaskListener((org.openide.util.Task task1) -> { mProgressHandle.finish(); if (mInterrupted) { mListener.onOperationInterrupted(); } }); mProgressHandle.start(); task.schedule(0); } private class OperationRunnable implements Runnable { private final ArrayList<File> mFilesWithErrors = new ArrayList<>(); private final Pattern mFolderByRegexPattern = Pattern.compile(mOptions.getFoldersRegex()); private final Map<String, Folder> mFolders = new HashMap<>(); private Kml mKml = new Kml(); private int mNumOfExif; private int mNumOfFiles; private int mNumOfGps; private final Folder mRootFolder; private final long mStartTime = System.currentTimeMillis(); public OperationRunnable() { mRootFolder = mKml.createAndSetFolder().withName(mOptions.getFoldersRootName()); mRootFolder.setDescription(mOptions.getFoldersRootDescription()); } @Override public void run() { mInterrupted = !generateFileList(); String status; if (!mInterrupted && !mFiles.isEmpty()) { mProgressHandle.switchToDeterminate(mFiles.size()); mListener.onOperationProcessingStarted(); status = String.format("\n%s\n", Dict.PROCESSING.getString()); mListener.onOperationLog(status); int progress = 0; for (File sourceFile : mFiles) { Toolbox.setStatusText(sourceFile.getAbsolutePath(), StatusDisplayer.IMPORTANCE_ERROR_HIGHLIGHT); mProgressHandle.progress(progress++); try { process(sourceFile); } catch (ImageProcessingException | IOException ex) { mFilesWithErrors.add(sourceFile); } if (Thread.interrupted()) { mInterrupted = true; break; } } } if (mInterrupted) { status = Dict.TASK_ABORTED.getString(); mListener.onOperationLog("\n" + status); } else { for (Exception exception : mExceptions) { mListener.onOperationLog(String.format("#%s", exception.getLocalizedMessage())); } if (!mFilesWithErrors.isEmpty()) { mListener.onOperationLog("\n" + mBundle.getString("ignoredFiles") + "\n"); for (File fileWithError : mFilesWithErrors) { logSuccess(false, fileWithError); } } if (!mFiles.isEmpty()) { saveToFile(); } } // if (mFiles.isEmpty()) { // mListener.onOperationFailed(); // } Toolbox.setStatusText("", StatusDisplayer.IMPORTANCE_ERROR_HIGHLIGHT); } private void addPhoto(StringBuilder sb, File sourceFile, ExifSubIFDDirectory exifDirectory) { Dimension originalDimension = null; try { originalDimension = GraphicsHelper.getImgageDimension(sourceFile); } catch (IOException ex) { Message.error(Dict.ERROR.getString(), ex.getLocalizedMessage()); } if (originalDimension == null) { originalDimension = new Dimension(200, 200); } Scaler scaler = new Scaler(new Dimension(originalDimension)); if (mOptions.isPhotoBalloonMaxWidth()) { scaler.setWidth(mOptions.getPhotoBalloonMaxWidthValue()); } if (mOptions.isPhotoBalloonMaxHeight()) { scaler.setHeight(mOptions.getPhotoBalloonMaxHeightValue()); } Dimension newDimension = scaler.getDimension(); String imageTagFormat = "<p><img src='%s' width='%d' height='%d'></p>"; String imageTag = String.format(imageTagFormat, getImagePath(sourceFile), newDimension.width, newDimension.height); sb.append(imageTag); } private boolean generateFileList() { mListener.onOperationLog(String.format("* %s", Dict.GENERATING_FILELIST.getString())); boolean invalidPath = false; if (mFiles != null) { for (File file : mFiles) { String path = file.getAbsolutePath(); if (path.contains(SystemUtils.PATH_SEPARATOR)) { invalidPath = true; String message = String.format(Dict.INVALID_PATH.getString(), file.getAbsolutePath()); mListener.onOperationLog(message); } } if (invalidPath) { String message = String.format(Dict.ERROR_PATH_SEPARATOR.getString(), SystemUtils.PATH_SEPARATOR); Message.error(Dict.IO_ERROR_TITLE.getString(), message); } } for (File file : mSourceFiles) { if (file.isDirectory()) { FileVisitor fileVisitor = new FileVisitor(mSourcePattern, mFiles); try { EnumSet<FileVisitOption> fileVisitOptions; if (mOptions.isSourceFollowLinks()) { fileVisitOptions = EnumSet.of(FileVisitOption.FOLLOW_LINKS); } else { fileVisitOptions = EnumSet.noneOf(FileVisitOption.class); } if (mOptions.isSourceRecursive()) { Files.walkFileTree(file.toPath(), fileVisitOptions, Integer.MAX_VALUE, fileVisitor); } else { Files.walkFileTree(file.toPath(), fileVisitOptions, 1, fileVisitor); } if (fileVisitor.isInterrupted()) { return false; } } catch (IOException ex) { Exceptions.printStackTrace(ex); } } else if (file.isFile() && mSourcePattern.matches(file.toPath().getFileName())) { mFiles.add(file); } } if (mFiles.isEmpty()) { mListener.onOperationLog("* " + Dict.FILELIST_EMPTY.getString()); mListener.onOperationFailed(Dict.FILELIST_EMPTY.getString()); } else { Collections.sort(mFiles); } return true; } private Folder getFolder(File file, Date date) { String key; Folder folder; if (mOptions.isFoldersSubFolders()) { if (mOptions.getFoldersBy() == 0) { key = file.getParentFile().getName(); } else if (mOptions.getFoldersBy() == 1) { try { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(mOptions.getFoldersDatePattern()); key = simpleDateFormat.format(date); } catch (Exception e) { key = "NULL"; } } else { key = mOptions.getFoldersRegexDefault(); Matcher matcher = mFolderByRegexPattern.matcher(file.getParent()); if (matcher.find()) { key = matcher.group(); } } folder = getFolder(key); } else { folder = mRootFolder; } return folder; } private Folder getFolder(String key) { Folder folder = mFolders.get(key); if (folder == null) { folder = new Folder().withName(key); mFolders.put(key, folder); } return folder; } private Date getImageDate(File file, ExifSubIFDDirectory exifDirectory) { Date date; if (exifDirectory.containsTag(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL)) { date = exifDirectory.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL); } else { long millis = 0; try { BasicFileAttributes attr = Files.readAttributes(file.toPath(), BasicFileAttributes.class); millis = attr.creationTime().toMillis(); } catch (IOException ex) { millis = file.lastModified(); } finally { date = new Date(millis); } } return date; } private String getImagePath(File file) { String imageSrc; if (mOptions.isPhotoBaseUrl()) { imageSrc = mOptions.getPhotoBaseUrlValue() + file.getName(); } else { Path relativePath = mDestinationFile.toPath().relativize(file.toPath()); // try { // relativeSourcePath = URLEncoder.encode(relativePath.toString(), "UTF-8"); // } catch (UnsupportedEncodingException ex) { // Exceptions.printStackTrace(ex); // } imageSrc = StringUtils.replace(relativePath.toString(), "..", ".", 1); if (SystemUtils.IS_OS_WINDOWS) { imageSrc = imageSrc.replace("\\", "/"); } } if (mOptions.isPhotoForceLowerCaseExtension()) { String ext = FilenameUtils.getExtension(imageSrc); if (!StringUtils.isBlank(ext) && !StringUtils.isAllLowerCase(ext)) { String noExt = FilenameUtils.removeExtension(imageSrc); imageSrc = String.format("%s.%s", noExt, ext.toLowerCase()); } } return imageSrc; } private void logSuccess(boolean b, File sourceFile) { String prefix = b ? "+" : "-"; mListener.onOperationLog(String.format(" %s %s", prefix, sourceFile.getAbsolutePath())); } private void process(File sourceFile) throws ImageProcessingException, IOException { Metadata metadata = ImageMetadataReader.readMetadata(sourceFile); ExifSubIFDDirectory exifDirectory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class); // ExifSubIFDDescriptor exifSubIFDDescriptor = new ExifSubIFDDescriptor(exifDirectory); GpsDirectory gpsDirectory = metadata.getFirstDirectoryOfType(GpsDirectory.class); GpsDescriptor gpsDescriptor = new GpsDescriptor(gpsDirectory); if (exifDirectory != null) { mNumOfExif++; if (gpsDirectory != null || mOptions.isPlacemarkIncludeNullCoordinate()) { mNumOfGps++; } } else { throw new ImageProcessingException( String.format(mBundle.getString("exifError"), sourceFile.getAbsolutePath())); } Date exifDate = getImageDate(sourceFile, exifDirectory); String name = FilenameUtils.getBaseName(sourceFile.getAbsolutePath()); if (mOptions.getPlacemarkNameBy() == 1) { try { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(mOptions.getPlacemarkDatePattern()); name = simpleDateFormat.format(exifDate); } catch (IllegalArgumentException ex) { // nvm - use filename } catch (NullPointerException ex) { System.err.println(" ! Invalid date in " + sourceFile.getAbsolutePath()); } } else if (mOptions.getPlacemarkNameBy() == 2) { name = ""; } StringBuilder descriptionBuilder = new StringBuilder(); // descriptionBuilder.append("<![CDATA["); if (mOptions.isDescriptionPhoto()) { addPhoto(descriptionBuilder, sourceFile, exifDirectory); } if (mOptions.isDescriptionFilename()) { descriptionBuilder.append(sourceFile.getName()).append("<br />"); } if (mOptions.isDescriptionDate()) { descriptionBuilder.append(Toolbox.getDefaultDateFormat().format(exifDate)).append("<br />"); } GeoLocation geoLocation = new GeoLocation(mOptions.getPlacemarkLat(), mOptions.getPlacemarkLon()); if (gpsDirectory != null) { geoLocation = gpsDirectory.getGeoLocation(); if (geoLocation == null) { throw new ImageProcessingException(sourceFile.getAbsolutePath()); } if (geoLocation.getLatitude() == 0 && geoLocation.getLongitude() == 0) { geoLocation = new GeoLocation(mOptions.getPlacemarkLat(), mOptions.getPlacemarkLon()); gpsDirectory = null; } if (exifDate != null) { if (mOptions.isDescriptionCoordinate()) { descriptionBuilder.append(gpsDescriptor.getDegreesMinutesSecondsDescription()) .append("<br />"); } if (mOptions.isDescriptionAltitude()) { descriptionBuilder.append(gpsDescriptor.getGpsAltitudeDescription()).append("<br />"); } if (mOptions.isDescriptionBearing()) { String bearing = gpsDescriptor.getGpsDirectionDescription(GpsDirectory.TAG_DEST_BEARING); if (bearing != null) { descriptionBuilder.append(bearing).append("<br />"); } } } } if (mOptions.isDescriptionCustom()) { descriptionBuilder.append(mOptions.getDescriptionCustomText()).append("<br />"); } // descriptionBuilder.append("]]>"); boolean shouldAppendToKml = gpsDirectory != null || mOptions.isPlacemarkIncludeNullCoordinate(); if (shouldAppendToKml) { double format = 1000000; int latInt = (int) (geoLocation.getLatitude() * format); int lonInt = (int) (geoLocation.getLongitude() * format); getFolder(sourceFile, exifDate).createAndAddPlacemark().withName(name).withOpen(Boolean.TRUE) .withDescription(descriptionBuilder.toString()).createAndSetPoint() .addToCoordinates(lonInt / format, latInt / format); logSuccess(shouldAppendToKml, sourceFile); } else { mFilesWithErrors.add(sourceFile); } } private void saveToFile() { List keys = new ArrayList(mFolders.keySet()); Collections.sort(keys); keys.stream().forEach((key) -> { mRootFolder.getFeature().add(mFolders.get((String) key)); }); mListener.onOperationLog( "\n" + String.format(Dict.SAVING.getString(), mDestinationFile.getAbsolutePath())); try { mKml.marshal(mDestinationFile); mKml = null; long millis = System.currentTimeMillis() - mStartTime; long min = TimeUnit.MILLISECONDS.toMinutes(millis); long sec = TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)); String status = String.format(mBundle.getString("operationSummary"), mNumOfGps, min, Dict.TIME_MIN.getString(), sec, Dict.TIME_SEC.getString()); if (!mOptions.isPhotoBaseUrl()) { status = status + "\n\n" + mBundle.getString("operationNote"); } mListener.onOperationLog("\n" + status); mListener.onOperationFinished(status); } catch (FileNotFoundException ex) { mListener.onOperationLog(ex.getLocalizedMessage()); } } } }