ValidObservation.java
/**
* VStar: a statistical analysis tool for variable star data.
* Copyright (C) 2009 AAVSO (http://www.aavso.org/)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.aavso.tools.vstar.data;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.aavso.tools.vstar.ui.mediator.AnalysisType;
import org.aavso.tools.vstar.ui.mediator.Mediator;
import org.aavso.tools.vstar.util.prefs.NumericPrecisionPrefs;
/**
* <p>
* This class corresponds to a single valid variable star observation. Depending
* upon the source, some fields may be null. Some are not permitted to be null
* however and these are documented below.
* </p>
*
* <p>
* For reference, here are the fields in the order they appear in the AAVSO
* download format:
* </p>
*
* <p>
* JD(0), MAGNITUDE(1), UNCERTAINTY(2), HQ_UNCERTAINTY(3), BAND(4),
* OBSERVER_CODE(5), COMMENT_CODE(6), COMP_STAR_1(7), COMP_STAR_2(8), CHARTS(9),
* COMMENTS(10), TRANSFORMED(11), AIRMASS(12), VALFLAG(13), CMAG(14), KMAG(15),
* HJD(16), NAME(17), AFFILIATION(18), MTYPE(19), GROUP(20), ADS_REFERENCE(21),
* DIGITIZER(22), CREDIT(23)
* </p>
*
* <p>
* The simple format file has these fields:
* </p>
*
* <p>
* JD MAGNITUDE [UNCERTAINTY] [OBSERVER_CODE] [VALFLAG]
* </p>
*
* <p>
* When VStar was first developed, observation source plugins were not
* anticipated, but should have been. The additional details members permit
* other typed information to be stored for an observation.
* </p>
*/
public class ValidObservation extends Observation {
public enum JDflavour {
UNKNOWN("Time"), JD("JD"), HJD("HJD"), BJD("BJD");
public final String label;
private JDflavour(String label) {
this.label = label;
}
}
// Julian Day, calendar date, and cache.
private DateInfo dateInfo = null;
private final static WeakHashMap<DateInfo, DateInfo> dateInfoCache;
static {
dateInfoCache = new WeakHashMap<DateInfo, DateInfo>();
}
// Magnitude, uncertainty, fainter/brighter-than, and cache.
private Magnitude magnitude = null;
private final static WeakHashMap<Magnitude, Magnitude> magnitudeCache;
static {
magnitudeCache = new WeakHashMap<Magnitude, Magnitude>();
}
private Double hqUncertainty = null;
private SeriesType band = null;
private SeriesType series = null; // series and band may differ on copy
// Comment codes and cache.
private CommentCodes commentCode = null;
private final static WeakHashMap<CommentCodes, CommentCodes> commentCodeCache;
static {
commentCodeCache = new WeakHashMap<CommentCodes, CommentCodes>();
}
private boolean transformed = false;
private ValidationType validationType = null;
// Heliocentric vs Geocentric Julian Date; uses dateInfo cache.
private DateInfo hJD = null;
private MTypeType mType = MTypeType.STD;
private String obsType = ObsType.UNKNOWN.getDescription();
// Phase values will be computed later, if a phase plot is requested.
// They may change over the lifetime of a ValidObservation instance
// since different epoch determination methods will result in different
// phase values.
private Double standardPhase = null;
private Double previousCyclePhase = null;
private boolean excluded = false;
private JDflavour jdFlavour = JDflavour.UNKNOWN;
// Optional string-based observation details.
private Map<String, Property> details;
// Optional observation detail titles, and shadow save collection.
private static Map<String, String> detailTitles = new HashMap<String, String>();
private static Map<String, String> savedDetailTitles = null;
// Optional observation detail types, and shadow save collection.
// @deprecated
private static Map<String, Class<?>> detailTypes = new HashMap<String, Class<?>>();
private static Map<String, Class<?>> savedDetailTypes = null;
// Ordering of keys via an index of insertion to titles table.
private static int detailIndex = 0;
private static int savedDetailIndex = 0;
private static Map<Integer, String> indexToDetailKey = new HashMap<Integer, String>();
private static Map<Integer, String> savedIndexToDetailKey = null;
private static Map<String, Integer> detailKeyToIndex = new HashMap<String, Integer>();
private static Map<String, Integer> savedDetailKeyToIndex = null;
private final static String nameKey = "NAME";
private final static String nameTitle = "Name";
private final static String obsCodeKey = "OBS_CODE";
private final static String obsCodeTitle = "Observer Code";
private final static String compStar1Key = "COMP_STAR1";
private final static String compStar1Title = "Comparison Star 1";
private final static String compStar2Key = "COMP_STAR2";
private final static String compStar2Title = "Comparison Star 2";
private final static String chartsKey = "CHARTS";
private final static String chartsTitle = "Charts";
private final static String commentsKey = "COMMENTS";
private final static String commentsTitle = "Comments";
private final static String airmassKey = "AIRMASS";
private final static String airmassTitle = "Airmass";
private final static String cMagKey = "CMAG";
private final static String cMagTitle = "CMag";
private final static String kMagKey = "KMAG";
private final static String kMagTitle = "KMag";
private final static String affiliationKey = "AFFILIATION";
private final static String affiliationTitle = "Affiliation";
private final static String groupKey = "GROUP";
private final static String groupTitle = "Group";
private final static String pubrefKey = "PUBREF";
private final static String pubrefTitle = "ADS Reference";
private final static String digitizerKey = "DIGTIZER";
private final static String digitizerTitle = "Digitizer";
private final static String creditKey = "CREDIT";
private final static String creditTitle = "Credit";
// The set of standard detail keys.
private final static Set<String> standardDetailKeys;
static {
standardDetailKeys = new HashSet<String>();
standardDetailKeys.add(nameKey);
standardDetailKeys.add(obsCodeKey);
standardDetailKeys.add(compStar1Key);
standardDetailKeys.add(compStar2Key);
standardDetailKeys.add(chartsKey);
standardDetailKeys.add(commentsKey);
standardDetailKeys.add(airmassKey);
standardDetailKeys.add(cMagKey);
standardDetailKeys.add(kMagKey);
standardDetailKeys.add(affiliationKey);
standardDetailKeys.add(groupKey);
standardDetailKeys.add(pubrefKey);
standardDetailKeys.add(digitizerKey);
standardDetailKeys.add(creditKey);
}
// A cache of detail values.
private static final WeakHashMap<Property, Property> detailValueCache;
static {
detailValueCache = new WeakHashMap<Property, Property>();
}
/**
* Constructor.
*
* All fields start out as null or false.
*/
public ValidObservation() {
super(0);
details = new HashMap<String, Property>();
}
/**
* Creates and returns a new ValidObservation instance that is a copy of the
* current instance.<br/>
* See https://github.com/AAVSO/VStar/issues/51
*
* @param type the series with which the copied observation will be associated
* (may be null)
* @return the new observation instance
*/
public ValidObservation copy(SeriesType series) {
ValidObservation ob = new ValidObservation();
ob.setJD(this.getJD());
ob.setJDflavour(this.getJDflavour());
ob.setMagnitude(this.getMagnitude().copy());
ob.setHqUncertainty(this.getHqUncertainty());
ob.setBand(this.getBand());
if (series != null) ob.setSeries(series);
ob.setCommentCode(this.getCommentCode());
ob.setTransformed(this.isTransformed());
ob.setValidationType(this.getValidationType());
ob.setHJD(this.getHJD());
ob.setMType(this.getMType());
ob.setObsType(this.getObsType());
ob.setStandardPhase(this.getStandardPhase());
ob.setPreviousCyclePhase(this.getPreviousCyclePhase());
ob.setExcluded(this.isExcluded());
ob.details = new HashMap<String, Property>(this.details);
return ob;
}
/**
* Creates and returns a new ValidObservation instance that is a copy of the
* current instance that is not associated with a different series.<br/>
*
* @return the new observation instance
*/
public ValidObservation copy() {
return copy(null);
}
/**
* Reset static non-cache maps and detail index in readiness for a new dataset.
*/
public static void reset() {
if (detailTitles != null) {
savedDetailTitles = new HashMap<String, String>(detailTitles);
detailTitles.clear();
}
if (detailTypes != null) {
savedDetailTypes = new HashMap<String, Class<?>>(detailTypes);
detailTypes.clear();
}
if (indexToDetailKey != null) {
savedIndexToDetailKey = new HashMap<Integer, String>(indexToDetailKey);
indexToDetailKey.clear();
}
if (detailKeyToIndex != null) {
savedDetailKeyToIndex = new HashMap<String, Integer>(detailKeyToIndex);
detailKeyToIndex.clear();
}
savedDetailIndex = detailIndex;
detailIndex = 0;
}
/**
* Restore static non-cache maps and detail index when a dataset load failure
* occurs.
*/
public static void restore() {
// Don't restore to null values, e.g. in the case of a first observation
// load failure, the saved map values may still be at their default of
// null.
if (savedDetailTitles != null) {
detailTitles = savedDetailTitles;
}
if (savedDetailTypes != null) {
detailTypes = savedDetailTypes;
}
if (savedIndexToDetailKey != null) {
indexToDetailKey = savedIndexToDetailKey;
}
if (savedDetailKeyToIndex != null) {
detailKeyToIndex = savedDetailKeyToIndex;
}
detailIndex = savedDetailIndex;
}
// Getters and Setters
/**
* Generic cached value getter.
*
* @param <T> The type of the cached value.
* @param cache The cache in which to look for the value.
* @param value The value to look up.
* @return The present or future cached value.
*/
private static <T> T getCachedValue(WeakHashMap<T, T> cache, T value) {
if (cache.containsKey(value)) {
value = cache.get(value);
} else {
cache.put(value, value);
}
return value;
}
/**
* @return details map
*/
public Map<String, Property> getDetails() {
return details;
}
/**
* @return the detailTitles
*/
public static Map<String, String> getDetailTitles() {
return detailTitles;
}
/**
* @return the detail types
*/
public static Map<String, Class<?>> getDetailTypes() {
return detailTypes;
}
/**
* Return the detail key given the detail ordering index.
*
* @param the detail index
* @return the detail key
*/
public static String getDetailKey(int index) {
return indexToDetailKey.get(index);
}
/**
* Return the detail index given the key.
*
* @param the detail key
* @return the detail index
*/
public static int getDetailIndex(String key) {
return detailKeyToIndex.get(key);
}
/**
* Add an observation detail, if the value is not null.
*
* @param key The detail key.
* @param value The detail property value.
* @param title The detail title, e.g. for use in table column, observation
* details.
*/
public void addDetail(String key, Property value, String title) {
if (key != null && value != null) {
value = getCachedValue(detailValueCache, value);
details.put(key, value);
if (!detailTitles.containsKey(key)) {
detailTitles.put(key, title);
detailTypes.put(key, value.getClazz());
indexToDetailKey.put(detailIndex, key);
detailKeyToIndex.put(key, detailIndex);
detailIndex++;
}
}
}
/**
* Add an observation detail, whose value is of type integer.
*
* @param key The detail key.
* @param value The detail integer value.
* @param title The detail title, e.g. for use in table column, observation
* details.
*/
public void addDetail(String key, Integer value, String title) {
addDetail(key, new Property(value), title);
}
/**
* Add an observation detail, whose value is of type real.
*
* @param key The detail key.
* @param value The detail real value.
* @param title The detail title, e.g. for use in table column, observation
* details.
*/
public void addDetail(String key, Double value, String title) {
addDetail(key, new Property(value), title);
}
/**
* Add an observation detail, whose value is of type Boolean.
*
* @param key The detail key.
* @param value The detail Boolean value.
* @param title The detail title, e.g. for use in table column, observation
* details.
*/
public void addDetail(String key, Boolean value, String title) {
addDetail(key, new Property(value), title);
}
/**
* Add an observation detail, whose value is of type string.
*
* @param key The detail key.
* @param value The detail string value.
* @param title The detail title, e.g. for use in table column, observation
* details.
*/
public void addDetail(String key, String value, String title) {
addDetail(key, new Property(value), title);
}
/**
* @return details map value given a key, if it exists, otherwise the empty
* string.
*/
public Property getDetail(String key) {
return detailExists(key) ? details.get(key) : Property.NO_VALUE;
}
/**
* @return detail titles map value given a key
*/
// public String getDetailTitle(String key) {
// return detailTitles.get(key);
// }
/**
* @return detail types map value given a key
*/
// public Class<?> getDetailType(String key) {
// return detailTypes.get(key);
// }
/**
* Does the specified detail key exist?
*
* @param key The detail key.
* @return Whether or not detail exists.
*/
public boolean detailExists(String key) {
return details.keySet().contains(key);
}
/**
* Does the specified detail key exist and is it non-empty?
*
* @param key The detail key.
* @return Whether or not non-empty detail exists.
*/
public boolean nonEmptyDetailExists(String key) {
return detailExists(key) && !isEmpty(key);
}
/**
* Does the specified detail title key exist?
*
* @param key The detail key.
* @return Whether or not the detail title exists.
*/
public boolean detailTitleExists(String key) {
return detailTitles.keySet().contains(key);
}
/**
* Does the specified detail key correspond to a standard detail key?
*
* @param key The detail key.
* @return Whether or not the detail key is standard.
*/
public boolean isStandardDetailKey(String key) {
return standardDetailKeys.contains(key);
}
/**
* @return the standarddetailkeys
*/
public static Set<String> getStandardDetailKeys() {
return standardDetailKeys;
}
/**
* @return the dateInfo
*/
public DateInfo getDateInfo() {
return dateInfo;
}
/**
* @param dateInfo the dateInfo to set
*/
public void setDateInfo(DateInfo dateInfo) {
this.dateInfo = getCachedValue(dateInfoCache, dateInfo);
}
/**
* @return the magnitude
*/
public Magnitude getMagnitude() {
return magnitude;
}
/**
* @param magnitude the magnitude to set
*/
public void setMagnitude(Magnitude magnitude) {
// this.magnitude = getCachedValue(magnitudeCache, magnitude);
this.magnitude = magnitude;
}
/**
* @param mag the magnitude component to set.
*/
public void setMag(double mag) {
// setMagnitude(new Magnitude(mag, magnitude.getUncertainty()));
this.magnitude.setMagValue(mag);
}
/**
* @return the obsCode
*/
public String getObsCode() {
return getDetail(obsCodeKey).getStrVal();
}
/**
* @param obsCode the obsCode to set
*/
public void setObsCode(String obsCode) {
addDetail(obsCodeKey, new Property(obsCode), obsCodeTitle);
}
/**
* @return whether this observation is discrepant
*/
public boolean isDiscrepant() {
return ValidationType.DISCREPANT.equals(validationType);
}
/**
* @param discrepant the discrepant to set
*/
public void setDiscrepant(boolean discrepant) {
// TODO: Should we keep a record of the last known value of
// this field before it was marked as discrepant? Right now,
// we are going from {G,D,P} -> D -> G -> D -> G ... so we are
// potentially losing information. This is a good candidate
// for undoable edits.
this.validationType = discrepant ? ValidationType.DISCREPANT : ValidationType.GOOD;
}
/**
* @return the name
*/
public String getName() {
return getDetail(nameKey).getStrVal();
}
/**
* @param name the name to set
*/
public void setName(String name) {
addDetail(nameKey, new Property(name), nameTitle);
}
/**
* @return the validationType
*/
public ValidationType getValidationType() {
return validationType;
}
/**
* @param validationType the validationType to set
*/
public void setValidationType(ValidationType validationType) {
this.validationType = validationType;
}
/**
* @return the hqUncertainty
*/
public Double getHqUncertainty() {
return hqUncertainty;
}
/**
* @param hqUncertainty the hqUncertainty to set
*/
public void setHqUncertainty(Double hqUncertainty) {
this.hqUncertainty = hqUncertainty;
}
/**
* @return the band
*/
public SeriesType getBand() {
return band;
}
/**
* @param band the band to set
*/
public void setBand(SeriesType band) {
this.band = band;
setSeries(band);
}
/**
* @return the series with which this observation is associated
*/
public SeriesType getSeries() {
return series;
}
/**
* @param series the series with which this observation is associated
*/
public void setSeries(SeriesType series) {
this.series = series;
}
/**
* @return the commentCode
*/
public CommentCodes getCommentCode() {
return commentCode;
}
/**
* @param commentCodeStr the comment code string to set
*/
public void setCommentCode(String commentCodeStr) {
this.commentCode = getCachedValue(commentCodeCache, new CommentCodes(commentCodeStr));
}
/**
* @param commentCodes the comment codes to set
*/
public void setCommentCode(CommentCodes commentCodes) {
this.commentCode = getCachedValue(commentCodeCache, commentCodes);
}
/**
* @return the compStar1
*/
public String getCompStar1() {
return getDetail(compStar1Key).getStrVal();
}
/**
* @param compStar1 the compStar1 to set
*/
public void setCompStar1(String compStar1) {
addDetail(compStar1Key, new Property(compStar1), compStar1Title);
}
/**
* @return the compStar2
*/
public String getCompStar2() {
return getDetail(compStar2Key).getStrVal();
}
/**
* @param compStar2 the compStar2 to set
*/
public void setCompStar2(String compStar2) {
addDetail(compStar2Key, new Property(compStar2), compStar2Title);
}
/**
* @return the charts
*/
public String getCharts() {
return getDetail(chartsKey).getStrVal();
}
/**
* @param charts the charts to set
*/
public void setCharts(String charts) {
addDetail(chartsKey, new Property(charts), chartsTitle);
}
/**
* @return the comments
*/
public String getComments() {
return getDetail(commentsKey).getStrVal();
}
/**
* @param comments the comments to set
*/
public void setComments(String comments) {
addDetail(commentsKey, new Property(comments), commentsTitle);
}
/**
* @return the transformed
*/
public boolean isTransformed() {
return transformed;
}
/**
* @param transformed the transformed to set
*/
public void setTransformed(boolean transformed) {
this.transformed = transformed;
}
/**
* @return the airmass
*/
public String getAirmass() {
return getDetail(airmassKey).getStrVal();
}
/**
* @param airmass the airmass to set
*/
public void setAirmass(String airmass) {
addDetail(airmassKey, new Property(airmass), airmassTitle);
}
/**
* @return the cMag
*/
public String getCMag() {
return getDetail(cMagKey).getStrVal();
}
/**
* @param cMag the cMag to set
*/
public void setCMag(String cMag) {
if ("0.ensemb".equals(cMag)) {
cMag = "Ensemble";
}
addDetail(cMagKey, new Property(cMag), cMagTitle);
}
/**
* @return the kMag
*/
public String getKMag() {
return getDetail(kMagKey).getStrVal();
}
/**
* @param kMag the kMag to set
*/
public void setKMag(String kMag) {
addDetail(kMagKey, new Property(kMag), kMagTitle);
}
/**
* @return the hJD
*/
public DateInfo getHJD() {
return hJD;
}
/**
* @param hJD the hJD to set
*/
public void setHJD(DateInfo hJD) {
this.hJD = getCachedValue(dateInfoCache, hJD);
}
/**
* @return the affiliation
*/
public String getAffiliation() {
return getDetail(affiliationKey).getStrVal();
}
/**
* @param affiliation the affiliation to set
*/
public void setAffiliation(String affiliation) {
addDetail(affiliationKey, new Property(affiliation), affiliationTitle);
}
/**
* @return the mType
*/
public MTypeType getMType() {
return mType;
}
/**
* @param mType the mType to set
*/
public void setMType(MTypeType mType) {
this.mType = mType;
}
/**
* @return the group
*/
public String getGroup() {
return getDetail(groupKey).getStrVal();
}
/**
* @param group the group of filters used for this observation
*/
public void setGroup(String group) {
addDetail(groupKey, new Property(group), groupTitle);
}
/**
* @return the obsType
*/
public String getObsType() {
return obsType;
}
/**
* @param obsType the obsType to set
*/
public void setObsType(String obsType) {
this.obsType = obsType;
}
/**
* @return the ADS Reference
*/
public String getADSRef() {
return getDetail(pubrefKey).getStrVal();
}
/**
* @param ADS Reference the ADS Reference to set
*/
public void setADSRef(String adsRef) {
addDetail(pubrefKey, new Property(adsRef), pubrefTitle);
}
/**
* @return the digitizer
*/
public String getDigitizer() {
return getDetail(digitizerKey).getStrVal();
}
/**
* @param digitizer the digitizer to set
*/
public void setDigitizer(String digitizer) {
addDetail(digitizerKey, new Property(digitizer), digitizerTitle);
}
/**
* @return the credit
*/
public String getCredit() {
return getDetail(creditKey).getStrVal();
}
/**
* @param credit the organization to be credited for the observation
*/
public void setCredit(String credit) {
addDetail(creditKey, new Property(credit), creditTitle);
}
/**
* @return the standardPhase
*/
public Double getStandardPhase() {
return standardPhase;
}
/**
* @param standardPhase the standardPhase to set
*/
public void setStandardPhase(Double standardPhase) {
this.standardPhase = standardPhase;
}
/**
* @return the previousCyclePhase
*/
public Double getPreviousCyclePhase() {
return previousCyclePhase;
}
/**
* @param previousCyclePhase the previousCyclePhase to set
*/
public void setPreviousCyclePhase(Double previousCyclePhase) {
this.previousCyclePhase = previousCyclePhase;
}
/**
* @return the excluded
*/
public boolean isExcluded() {
return excluded;
}
/**
* @param excluded the excluded to set
*/
public void setExcluded(boolean excluded) {
this.excluded = excluded;
}
/**
* @return true if Heliocentric
*/
public boolean isHeliocentric() {
return jdFlavour == JDflavour.HJD;
}
/**
* @return true if Barycentric
*/
public boolean isBarycentric() {
return jdFlavour == JDflavour.BJD;
}
public JDflavour getJDflavour() {
return jdFlavour;
}
public void setJDflavour(JDflavour jdFlavour) {
this.jdFlavour = jdFlavour;
}
public String getTimeUnits() {
return jdFlavour.label;
}
// Output formatting methods.
public String toString() {
StringBuffer strBuf = new StringBuffer();
if (nonEmptyDetailExists(nameKey)) {
strBuf.append(details.get(nameKey));
strBuf.append("\n");
}
if (dateInfo != null) {
strBuf.append(getTimeUnits());
strBuf.append(": ");
strBuf.append(NumericPrecisionPrefs.formatTime(dateInfo.getJulianDay()));
strBuf.append("\n");
strBuf.append("Calendar Date: ");
strBuf.append(dateInfo.getCalendarDate());
strBuf.append("\n");
}
// If we are not in phase plot mode, we should not represent ourselves
// as having a phase.
if (Mediator.getInstance().getAnalysisType() == AnalysisType.PHASE_PLOT) {
if (standardPhase != null) {
strBuf.append("Standard Phase: ");
strBuf.append(NumericPrecisionPrefs.formatTime(standardPhase));
strBuf.append("\n");
}
if (previousCyclePhase != null) {
strBuf.append("Previous Cycle Phase: ");
strBuf.append(NumericPrecisionPrefs.formatTime(previousCyclePhase));
strBuf.append("\n");
}
}
strBuf.append("Magnitude: ");
strBuf.append(magnitude.toString());
strBuf.append("\n");
if (hqUncertainty != null) {
strBuf.append("HQ Uncertainty: ");
strBuf.append(NumericPrecisionPrefs.formatMag(hqUncertainty));
strBuf.append("\n");
}
if (validationType != null) {
strBuf.append("Validation: ");
strBuf.append(validationType.toString());
strBuf.append("\n");
}
if (obsType != null) {
strBuf.append("Observation Type: ");
strBuf.append(obsType);
strBuf.append("\n");
}
if (band != null) {
strBuf.append("Band: ");
strBuf.append(band.getDescription());
strBuf.append("\n");
}
if (nonEmptyDetailExists(obsCodeKey)) {
strBuf.append(detailTitles.get(obsCodeKey) + ": ");
strBuf.append(details.get(obsCodeKey));
strBuf.append("\n");
}
if (commentCode != null) {
String str = getCommentCode().getOrigString();
if (str.trim().length() != 0) {
strBuf.append("Comment Codes:\n");
strBuf.append("[");
strBuf.append(str);
strBuf.append("]\n");
strBuf.append(commentCode.toString());
}
}
if (nonEmptyDetailExists(compStar1Key)) {
strBuf.append(detailTitles.get(compStar1Key) + ": ");
strBuf.append(details.get(compStar1Key));
strBuf.append("\n");
}
if (nonEmptyDetailExists(compStar2Key)) {
strBuf.append(detailTitles.get(compStar2Key) + ": ");
strBuf.append(details.get(compStar2Key));
strBuf.append("\n");
}
if (nonEmptyDetailExists(chartsKey)) {
strBuf.append(detailTitles.get(chartsKey) + ": ");
strBuf.append(details.get(chartsKey));
strBuf.append("\n");
}
if (nonEmptyDetailExists(commentsKey)) {
strBuf.append(detailTitles.get(commentsKey) + ": ");
strBuf.append(details.get(commentsKey));
strBuf.append("\n");
}
if (transformed) {
strBuf.append("Transformed: yes\n");
}
if (nonEmptyDetailExists(airmassKey)) {
strBuf.append(detailTitles.get(airmassKey) + ": ");
strBuf.append(details.get(airmassKey));
strBuf.append("\n");
}
if (nonEmptyDetailExists(cMagKey)) {
strBuf.append(detailTitles.get(cMagKey) + ": ");
strBuf.append(details.get(cMagKey));
strBuf.append("\n");
}
if (nonEmptyDetailExists(kMagKey)) {
strBuf.append(detailTitles.get(kMagKey) + ": ");
strBuf.append(details.get(kMagKey));
strBuf.append("\n");
}
if (hJD != null) {
strBuf.append("Heliocentric Julian Day: ");
strBuf.append(NumericPrecisionPrefs.formatTime(hJD.getJulianDay()));
strBuf.append("\n");
}
if (nonEmptyDetailExists(groupKey)) {
strBuf.append(detailTitles.get(groupKey) + ": ");
strBuf.append(details.get(groupKey));
strBuf.append("\n");
}
if (nonEmptyDetailExists(pubrefKey)) {
strBuf.append(detailTitles.get(pubrefKey) + ": ");
strBuf.append(details.get(pubrefKey));
strBuf.append("\n");
}
if (nonEmptyDetailExists(digitizerKey)) {
strBuf.append(detailTitles.get(digitizerKey) + ": ");
strBuf.append(details.get(digitizerKey));
strBuf.append("\n");
}
if (nonEmptyDetailExists(creditKey)) {
strBuf.append(detailTitles.get(creditKey) + ": ");
strBuf.append(details.get(creditKey));
strBuf.append("\n");
}
// Add any remaining non-AAVSO details, e.g. for a plugin.
for (String key : details.keySet()) {
if (!standardDetailKeys.contains(key)) {
strBuf.append(detailTitles.get(key) + ": ");
strBuf.append(details.get(key));
strBuf.append("\n");
}
}
return strBuf.toString();
}
/**
* Returns a line in TSV format of the following fields (bracketed fields are
* optional):
*
* [Phase,]JD,MAGNITUDE,[UNCERTAINTY],[OBSERVER_CODE],[VALFLAG]
*
* @param delimiter The field delimiter to use.
*/
public String toSimpleFormatString(String delimiter) {
return toSimpleFormatString(delimiter, true);
}
/**
* Returns a line in TSV format of the following fields (bracketed fields are
* optional):
*
* [Phase,][JD,]MAGNITUDE,[UNCERTAINTY],[OBSERVER_CODE],[VALFLAG]
*
* @param delimiter The field delimiter to use.
* @param includeJD Should the JD be included in the output?
*/
public String toSimpleFormatString(String delimiter, boolean includeJD) {
StringBuffer buf = new StringBuffer();
if (Mediator.getInstance().getAnalysisType() == AnalysisType.PHASE_PLOT) {
buf.append(this.getStandardPhase());
buf.append(delimiter);
}
if (includeJD && this.getDateInfo() != null) {
buf.append(this.getDateInfo().getJulianDay());
buf.append(delimiter);
}
buf.append(this.getMagnitude().isFainterThan() ? "<" : "");
buf.append(this.getMagnitude().getMagValue());
buf.append(delimiter);
double uncertainty = this.getMagnitude().getUncertainty();
// TODO: why != here and > in next method?
if (uncertainty != 0.0) {
buf.append(uncertainty);
}
buf.append(delimiter);
if (nonEmptyDetailExists(obsCodeKey)) {
buf.append(quoteForCSVifNeeded(details.get(obsCodeKey).toString(), delimiter));
}
buf.append(delimiter);
if (this.validationType != null) {
buf.append(this.validationType.getValflag());
}
buf.append("\n");
return buf.toString();
}
/**
* Returns a line in delimiter-separator (TSV, CSV, ...) AAVSO download format.
*
* @param delimiter The field delimiter to use.
*/
public String toAAVSOFormatString(String delimiter) {
return toAAVSOFormatString(delimiter, true);
}
/**
* Returns a line in delimiter-separator (TSV, CSV, ...) AAVSO download format.
*
* @param delimiter The field delimiter to use.
* @param includeJD Should the JD be included in the output?
*/
public String toAAVSOFormatString(String delimiter, boolean includeJD) {
StringBuffer buf = new StringBuffer();
if (Mediator.getInstance().getAnalysisType() == AnalysisType.PHASE_PLOT) {
buf.append(this.getStandardPhase());
buf.append(delimiter);
}
if (includeJD && this.getDateInfo() != null) {
buf.append(this.getDateInfo().getJulianDay());
buf.append(delimiter);
}
buf.append(this.getMagnitude().isFainterThan() ? "<" : "");
buf.append(this.getMagnitude().getMagValue());
buf.append(delimiter);
double uncertainty = this.getMagnitude().getUncertainty();
if (uncertainty > 0.0) {
buf.append(uncertainty);
}
buf.append(delimiter);
if (this.getHqUncertainty() != null) {
double hqUncertainty = this.getHqUncertainty();
if (hqUncertainty > 0.0) {
buf.append(hqUncertainty);
}
}
buf.append(delimiter);
buf.append(quoteForCSVifNeeded(this.getBand().getShortName(), delimiter));
buf.append(delimiter);
if (nonEmptyDetailExists(obsCodeKey)) {
buf.append(quoteForCSVifNeeded(details.get(obsCodeKey).toString(), delimiter));
}
buf.append(delimiter);
if (this.getCommentCode() != null) {
buf.append(quoteForCSVifNeeded(this.getCommentCode().getOrigString(), delimiter));
}
buf.append(delimiter);
if (this.getCompStar1() != null) {
buf.append(quoteForCSVifNeeded(this.getCompStar1(), delimiter));
}
buf.append(delimiter);
if (this.getCompStar2() != null) {
buf.append(quoteForCSVifNeeded(this.getCompStar2(), delimiter));
}
buf.append(delimiter);
if (this.getCharts() != null) {
buf.append(quoteForCSVifNeeded(this.getCharts(), delimiter));
}
buf.append(delimiter);
if (this.getComments() != null) {
buf.append(quoteForCSVifNeeded(this.getComments(), delimiter));
}
buf.append(delimiter);
buf.append(this.isTransformed() ? "Yes" : "No");
buf.append(delimiter);
if (this.getAirmass() != null) {
buf.append(this.getAirmass());
}
buf.append(delimiter);
if (this.validationType != null) {
buf.append(this.validationType.getValflag());
}
buf.append(delimiter);
if (this.getCMag() != null) {
buf.append(this.getCMag());
}
buf.append(delimiter);
if (this.getKMag() != null) {
buf.append(this.getKMag());
}
buf.append(delimiter);
if (this.getHJD() != null) {
buf.append(hJD.getJulianDay());
}
buf.append(delimiter);
buf.append(quoteForCSVifNeeded(!isEmpty(this.getName()) ? this.getName() : "Unknown", delimiter));
buf.append(delimiter);
// Affiliation
if (getAffiliation() != null) {
buf.append(quoteForCSVifNeeded(getAffiliation(), delimiter));
}
buf.append(delimiter);
buf.append(quoteForCSVifNeeded(this.getMType() != null ? this.getMType().getShortName() : MTypeType.STD.getShortName(), delimiter));
buf.append(delimiter);
// Group
if (getGroup() != null) {
buf.append(quoteForCSVifNeeded(getGroup(), delimiter));
}
buf.append(delimiter);
// ADS Reference
if (getADSRef() != null) {
buf.append(quoteForCSVifNeeded(getADSRef(), delimiter));
}
buf.append(delimiter);
// Digitizer
if (getDigitizer() != null) {
buf.append(quoteForCSVifNeeded(getDigitizer(), delimiter));
}
buf.append(delimiter);
// Credit
if (getCredit() != null) {
buf.append(quoteForCSVifNeeded(getCredit(), delimiter));
}
buf.append(delimiter);
// ObsType
// TODO: in AID but not yet AAVSO download format!
// buf.append(delimiter);
// TODO: handle reading in aavso text and aid obs readers
buf.append("\n");
return buf.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((band == null) ? 0 : band.hashCode());
result = prime * result + ((commentCode == null) ? 0 : commentCode.hashCode());
result = prime * result + ((dateInfo == null) ? 0 : dateInfo.hashCode());
result = prime * result + ((details == null) ? 0 : details.hashCode());
result = prime * result + (excluded ? 1231 : 1237);
result = prime * result + ((hJD == null) ? 0 : hJD.hashCode());
result = prime * result + ((hqUncertainty == null) ? 0 : hqUncertainty.hashCode());
result = prime * result + ((jdFlavour == null) ? 0 : jdFlavour.hashCode());
result = prime * result + ((mType == null) ? 0 : mType.hashCode());
result = prime * result + ((magnitude == null) ? 0 : magnitude.hashCode());
result = prime * result + ((obsType == null) ? 0 : obsType.hashCode());
result = prime * result + ((previousCyclePhase == null) ? 0 : previousCyclePhase.hashCode());
result = prime * result + ((series == null) ? 0 : series.hashCode());
result = prime * result + ((standardPhase == null) ? 0 : standardPhase.hashCode());
result = prime * result + (transformed ? 1231 : 1237);
result = prime * result + ((validationType == null) ? 0 : validationType.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ValidObservation other = (ValidObservation) obj;
if (band == null) {
if (other.band != null)
return false;
} else if (!band.equals(other.band))
return false;
if (commentCode == null) {
if (other.commentCode != null)
return false;
} else if (!commentCode.equals(other.commentCode))
return false;
if (dateInfo == null) {
if (other.dateInfo != null)
return false;
} else if (!dateInfo.equals(other.dateInfo))
return false;
if (details == null) {
if (other.details != null)
return false;
} else if (!details.equals(other.details))
return false;
if (excluded != other.excluded)
return false;
if (hJD == null) {
if (other.hJD != null)
return false;
} else if (!hJD.equals(other.hJD))
return false;
if (hqUncertainty == null) {
if (other.hqUncertainty != null)
return false;
} else if (!hqUncertainty.equals(other.hqUncertainty))
return false;
if (jdFlavour != other.jdFlavour)
return false;
if (mType != other.mType)
return false;
if (magnitude == null) {
if (other.magnitude != null)
return false;
} else if (!magnitude.equals(other.magnitude))
return false;
if (obsType == null) {
if (other.obsType != null)
return false;
} else if (!obsType.equals(other.obsType))
return false;
if (previousCyclePhase == null) {
if (other.previousCyclePhase != null)
return false;
} else if (!previousCyclePhase.equals(other.previousCyclePhase))
return false;
if (series == null) {
if (other.series != null)
return false;
} else if (!series.equals(other.series))
return false;
if (standardPhase == null) {
if (other.standardPhase != null)
return false;
} else if (!standardPhase.equals(other.standardPhase))
return false;
if (transformed != other.transformed)
return false;
if (validationType != other.validationType)
return false;
return true;
}
// Convenience methods.
public double getJD() {
return this.dateInfo.getJulianDay();
}
public void setJD(double jd) {
setDateInfo(new DateInfo(jd));
}
public double getMag() {
return this.magnitude.getMagValue();
}
// Helpers
private boolean isEmpty(String s) {
return s == null || s.trim().length() == 0;
}
/**
* Precede any double quote in the argument with another and
* wrap the whole argument in double quotes.
*
* @param field The field to be quoted
* @return The quoted field
*/
public static String quoteForCSV(String field) {
return "\"" + field.replace("\"", "\"\"") + "\"";
}
/**
* Double-quote the argument only if the delimiter occurs in the argument.
*
* @param field The field to be quoted
* @param delimiter field delimiter
* @return The quoted field
*/
public static String quoteForCSVifNeeded(String field, String delimiter) {
return field.contains(delimiter) ? quoteForCSV(field) : field;
}
}