Thứ Hai, 23 tháng 12, 2013

Determining Presence of Characters or Integers in String with Guava CharMatcher and Apache Commons Lang StringUtils

A recent Reddit post asked the question, "Is there a predefined method for checking if a variable value contains a particular character or integer?" That question-based title was also asked a different way, "A method or quick way for checking if a variable contains any numbers say or ('x',2,'B') like a list?" I am not aware of any single method call within the standard SDK libraries to do this (other than using a carefully designed regular expression), but in this post I answer those questions using Guava's CharMatcher and Apache Common Lang's StringUtils class.

Java's String class does have a contains method that can be used to determine if a single character is contained in that String or if a certain explicitly specified sequence of characters is contained in that String. However, I'm not aware of any way in a single executable statement (not counting regular expressions) to ask Java if a given String contains any of a specified set of characters without needing to contain all of them or contain them in the specified order. Both Guava and Apache Commons Lang do provide mechanisms for just this thing.

Apache Commons Lang (version 3.1 used in this post) provides overloaded StringUtils.containsAny methods that easily accomplish this request. Both overloaded versions expect the first parameter passed to them to be the String (or more precisely, the CharSequence) to be tested to see if it contains a given letter or integer. The first overloaded version, StringUtils.containsAny(CharSequence, char...) accepts zero or more char elements to be tested to see if any of them are in the String represented by the first argument. The second overloaded version, StringUtils.containsAny(CharSequence, CharSequence) expects the second argument to contain all the potential characters to be searched for in the first argument as a single sequence of characters.

The following code listing demonstrates using this Apache Commons Lang approach to determine if a given string contains certain characters. All three statements will pass their assertions because "Inspired by Actual Events" does include 'd' and 'A', but not 'Q'. Because it is only necessary for any one of the provided characters to be present to return true, the first two assertions of true pass. The third assertion passes because the string does NOT contain the only provided letter and so the negative is asserted.

Determining String Contains A Character with StringUtils

private static void demoStringContainingLetterInStringUtils()
{
assert StringUtils.containsAny("Inspired by Actual Events", 'd', 'A'); // true: both contained
assert StringUtils.containsAny("Inspired by Actual Events", 'd', 'Q'); // true: one contained
assert !StringUtils.containsAny("Inspired by Actual Events", 'Q'); // true: none contained (!)
}

Guava's CharMatcher can also be used in a similar manner as demonstrated in the next code listing.

Determining String Contains A Character with CharMatcher

private static void demoStringContainingLetterInGuava()
{
assert CharMatcher.anyOf("Inspired by Actual Events").matchesAnyOf(new String(new char[]{'d', 'A'}));
assert CharMatcher.anyOf("Inspired by Actual Events").matchesAnyOf(new String (new char[] {'d', 'Q'}));
assert !CharMatcher.anyOf("Inspired by Actual Events").matchesAnyOf(new String(new char[]{'Q'}));
}

What if we specifically want to make sure at least one character in a given String/CharSequence is a numeric (integer), but we cannot be guaranteed that the entire string is numerics? The same approach as used above with Apache Commons Lang's StringUtils can be applied here with the only change being that the provided letters to be matched are the numeric digits 0 through 9. This is shown in the next screen snapshot.

Determining String Contains a Numeral with StringUtils

private static void demoStringContainingNumericDigitInStringUtils()
{
assert !StringUtils.containsAny("Inspired by Actual Events", "0123456789");
assert StringUtils.containsAny("Inspired by Actual Events 2013", "0123456789");
}

Guava's CharMatcher has a really slick way of expressing this question of whether a provided sequence of characters includes at least one numeral. This is shown in the next code listing.

Determining String Contains a Numeral with CharMatcher

private static void demoStringContainingNumericDigitInGuava()
{
assert !CharMatcher.DIGIT.matchesAnyOf("Inspired by Actual Events");
assert CharMatcher.DIGIT.matchesAnyOf("Inspired by Actual Events 2013");
}

CharMatcher.DIGIT provides a concise and expressive approach to specifying that we want to match a digit. Fortunately, CharMatcher provides numerous other public fields similar to DIGIT for convenience in determining if strings contain other types of characters.

For completeness, I have included the single class containing all of the above examples in the next code listing. This class's main() function can be run with the -enableassertions (or -ea) flag set on the Java launcher and will complete without any AssertionErrors.

StringContainsDemonstrator.java

package dustin.examples.strings;

import com.google.common.base.CharMatcher;
import static java.lang.System.out;

import org.apache.commons.lang3.StringUtils;

/**
* Demonstrate Apache Commons Lang StringUtils and Guava's CharMatcher. This
* class exists to demonstrate Apache Commons Lang StringUtils and Guava's
* CharMatcher support for determining if a particular character or set of
* characters or integers is contained within a given
*
* This class's tests depend on asserts being enabled, so specify the JVM option
* -enableassertions (-ea) when running this example.
*
* @author Dustin
*/
public class StringContainsDemonstrator
{
private static final String CANDIDATE_STRING = "Inspired by Actual Events";
private static final String CANDIDATE_STRING_WITH_NUMERAL = CANDIDATE_STRING + " 2013";
private static final char FIRST_CHARACTER = 'd';
private static final char SECOND_CHARACTER = 'A';
private static final String CHARACTERS = new String(new char[]{FIRST_CHARACTER, SECOND_CHARACTER});
private static final char NOT_CONTAINED_CHARACTER = 'Q';
private static final String NOT_CONTAINED_CHARACTERS = new String(new char[]{NOT_CONTAINED_CHARACTER});
private static final String MIXED_CONTAINED_CHARACTERS = new String (new char[] {FIRST_CHARACTER, NOT_CONTAINED_CHARACTER});
private static final String NUMERIC_CHARACTER_SET = "0123456789";

private static void demoStringContainingLetterInGuava()
{
assert CharMatcher.anyOf(CANDIDATE_STRING).matchesAnyOf(CHARACTERS);
assert CharMatcher.anyOf(CANDIDATE_STRING).matchesAnyOf(MIXED_CONTAINED_CHARACTERS);
assert !CharMatcher.anyOf(CANDIDATE_STRING).matchesAnyOf(NOT_CONTAINED_CHARACTERS);
}

private static void demoStringContainingNumericDigitInGuava()
{
assert !CharMatcher.DIGIT.matchesAnyOf(CANDIDATE_STRING);
assert CharMatcher.DIGIT.matchesAnyOf(CANDIDATE_STRING_WITH_NUMERAL);
}

private static void demoStringContainingLetterInStringUtils()
{
assert StringUtils.containsAny(CANDIDATE_STRING, FIRST_CHARACTER, SECOND_CHARACTER);
assert StringUtils.containsAny(CANDIDATE_STRING, FIRST_CHARACTER, NOT_CONTAINED_CHARACTER);
assert !StringUtils.containsAny(CANDIDATE_STRING, NOT_CONTAINED_CHARACTER);
}

private static void demoStringContainingNumericDigitInStringUtils()
{
assert !StringUtils.containsAny(CANDIDATE_STRING, NUMERIC_CHARACTER_SET);
assert StringUtils.containsAny(CANDIDATE_STRING_WITH_NUMERAL, NUMERIC_CHARACTER_SET);
}

/**
* Indicate whether assertions are enabled.
*
* @return {@code true} if assertions are enabled or {@code false} if
* assertions are not enabled (are disabled).
*/
private static boolean areAssertionsEnabled()
{
boolean enabled = false;
assert enabled = true;
return enabled;
}

/**
* Main function for running methods to demonstrate Apache Commons Lang
* StringUtils and Guava's CharMatcher support for determining if a particular
* character or set of characters or integers is contained within a given
* String.
*
* @param args the command line arguments Command line arguments; none expected.
*/
public static void main(String[] args)
{
if (!areAssertionsEnabled())
{
out.println("This class cannot demonstrate anything without assertions enabled.");
out.println("\tPlease re-run with assertions enabled (-ea).");
System.exit(-1);
}

out.println("Beginning demonstrations...");
demoStringContainingLetterInGuava();
demoStringContainingLetterInStringUtils();
demoStringContainingNumericDigitInGuava();
demoStringContainingNumericDigitInStringUtils();
out.println("...Demonstrations Ended");
}
}

Guava and Apache Commons Lang are very popular with Java developers because of the methods they provide beyond what the SDK provides that Java developers commonly need. In this post, I looked at how Guava's CharMatcher and Apache Commons Lang's StringUtils can be used to concisely but expressively test to determine if any of a set of specified characters exists within a provided string.

Orika: Mapping JAXB Objects to Business/Domain Objects

This post looks at mapping JAXB objects to business domain objects with Orika. Earlier this month, I covered the same mapping use case using reflection-based Dozer. In this post, I'll assume the same example classes need to be mapped, but they will be mapped using Orika instead of Dozer.

Dozer and Orika are intended to solve the same type of problem: the automatic mapping of two "data" objects that do not share a common inheritance but represent the same same of data fields. Dozer uses reflection to accomplish this while Orika uses reflection and bytecode manipulation to accomplish it. Orika's slogan is, "simpler, lighter and faster Java bean mapping."

Orika has an Apache License, Version 2, and can be downloaded at https://github.com/orika-mapper/orika/archive/master.zip (sources) or at http://search.maven.org/#search|ga|1|orika (binaries). Orika has dependencies on Javassist (for bytecode manipulation), SLF4J, and paranamer (to access method/constructor parameter names at runtime). Two of these three dependencies (JavaAssist and paranamer but not SLF4J) are bundled in orika-core-1.4.4-deps-included.jar. If the dependencies are already available, the slimmer orika-core-1.4.4.jar can be used instead. As the names of these JARs suggest, I'm using Orika 1.4.4 for my examples in this post.

In my post Dozer: Mapping JAXB Objects to Business/Domain Objects, I discussed reasons that using instances of JAXB-generatated classes as business or domain objects is often not desirable. I then showed "traditional" ways of mapping between JAXB-generated classes and custom data classes so that data could be passed throughout an application in the business domain data objects. For this post, I will be using the same approach, but with Orika doing the mapping rather than doing custom mapping or using Dozer for the mapping. For convenience, I include the cost listings here for the JAXB-generated classes com.blogspot.marxsoftware.AddressType and com.blogspot.marxsoftware.PersonType as well as the renamed custom data classes dustin.examples.orikademo.Address and dustin.examples.orikademo.Person.

JAXB-generated AddressType.java

//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.4-2
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2013.12.03 at 11:44:32 PM MST
//


package com.blogspot.marxsoftware;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;


/**
* <p>Java class for AddressType complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* <complexType name="AddressType">
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <attribute name="streetAddress1" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* <attribute name="streetAddress2" type="{http://www.w3.org/2001/XMLSchema}string" />
* <attribute name="city" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* <attribute name="state" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* <attribute name="zipcode" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* </restriction>
* </complexContent>
* </complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "AddressType")
public class AddressType {

@XmlAttribute(name = "streetAddress1", required = true)
protected String streetAddress1;
@XmlAttribute(name = "streetAddress2")
protected String streetAddress2;
@XmlAttribute(name = "city", required = true)
protected String city;
@XmlAttribute(name = "state", required = true)
protected String state;
@XmlAttribute(name = "zipcode", required = true)
protected String zipcode;

/**
* Gets the value of the streetAddress1 property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getStreetAddress1() {
return streetAddress1;
}

/**
* Sets the value of the streetAddress1 property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setStreetAddress1(String value) {
this.streetAddress1 = value;
}

/**
* Gets the value of the streetAddress2 property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getStreetAddress2() {
return streetAddress2;
}

/**
* Sets the value of the streetAddress2 property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setStreetAddress2(String value) {
this.streetAddress2 = value;
}

/**
* Gets the value of the city property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getCity() {
return city;
}

/**
* Sets the value of the city property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setCity(String value) {
this.city = value;
}

/**
* Gets the value of the state property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getState() {
return state;
}

/**
* Sets the value of the state property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setState(String value) {
this.state = value;
}

/**
* Gets the value of the zipcode property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getZipcode() {
return zipcode;
}

/**
* Sets the value of the zipcode property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setZipcode(String value) {
this.zipcode = value;
}

}
JAXB-generated PersonType.java

//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.4-2
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2013.12.03 at 11:44:32 PM MST
//


package com.blogspot.marxsoftware;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;


/**
* <p>Java class for PersonType complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* <complexType name="PersonType">
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <sequence>
* <element name="MailingAddress" type="{http://marxsoftware.blogspot.com/}AddressType"/>
* <element name="ResidentialAddress" type="{http://marxsoftware.blogspot.com/}AddressType" minOccurs="0"/>
* </sequence>
* <attribute name="firstName" type="{http://www.w3.org/2001/XMLSchema}string" />
* <attribute name="lastName" type="{http://www.w3.org/2001/XMLSchema}string" />
* </restriction>
* </complexContent>
* </complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "PersonType", propOrder = {
"mailingAddress",
"residentialAddress"
})
public class PersonType {

@XmlElement(name = "MailingAddress", required = true)
protected AddressType mailingAddress;
@XmlElement(name = "ResidentialAddress")
protected AddressType residentialAddress;
@XmlAttribute(name = "firstName")
protected String firstName;
@XmlAttribute(name = "lastName")
protected String lastName;

/**
* Gets the value of the mailingAddress property.
*
* @return
* possible object is
* {@link AddressType }
*
*/
public AddressType getMailingAddress() {
return mailingAddress;
}

/**
* Sets the value of the mailingAddress property.
*
* @param value
* allowed object is
* {@link AddressType }
*
*/
public void setMailingAddress(AddressType value) {
this.mailingAddress = value;
}

/**
* Gets the value of the residentialAddress property.
*
* @return
* possible object is
* {@link AddressType }
*
*/
public AddressType getResidentialAddress() {
return residentialAddress;
}

/**
* Sets the value of the residentialAddress property.
*
* @param value
* allowed object is
* {@link AddressType }
*
*/
public void setResidentialAddress(AddressType value) {
this.residentialAddress = value;
}

/**
* Gets the value of the firstName property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getFirstName() {
return firstName;
}

/**
* Sets the value of the firstName property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setFirstName(String value) {
this.firstName = value;
}

/**
* Gets the value of the lastName property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getLastName() {
return lastName;
}

/**
* Sets the value of the lastName property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setLastName(String value) {
this.lastName = value;
}

}
Domain/Business Class Address.java

package dustin.examples.orikademo;

import java.util.Objects;

/**
* Address class.
*
* @author Dustin
*/
public class Address
{
private String streetAddress1;
private String streetAddress2;
private String municipality;
private String state;
private String zipCode;

public Address() {}

public Address(
final String newStreetAddress1,
final String newStreetAddress2,
final String newMunicipality,
final String newState,
final String newZipCode)
{
this.streetAddress1 = newStreetAddress1;
this.streetAddress2 = newStreetAddress2;
this.municipality = newMunicipality;
this.state = newState;
this.zipCode = newZipCode;
}

public String getStreetAddress1()
{
return this.streetAddress1;
}

public void setStreetAddress1(String streetAddress1)
{
this.streetAddress1 = streetAddress1;
}

public String getStreetAddress2()
{
return this.streetAddress2;
}

public void setStreetAddress2(String streetAddress2)
{
this.streetAddress2 = streetAddress2;
}

public String getMunicipality()
{
return this.municipality;
}

public void setMunicipality(String municipality)
{
this.municipality = municipality;
}

public String getState() {
return this.state;
}

public void setState(String state)
{
this.state = state;
}

public String getZipCode()
{
return this.zipCode;
}

public void setZipCode(String zipCode)
{
this.zipCode = zipCode;
}

@Override
public int hashCode()
{
return Objects.hash(
this.streetAddress1, this.streetAddress2, this.municipality,
this.state, this.zipCode);
}

@Override
public boolean equals(Object obj)
{
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Address other = (Address) obj;
if (!Objects.equals(this.streetAddress1, other.streetAddress1))
{
return false;
}
if (!Objects.equals(this.streetAddress2, other.streetAddress2))
{
return false;
}
if (!Objects.equals(this.municipality, other.municipality))
{
return false;
}
if (!Objects.equals(this.state, other.state))
{
return false;
}
if (!Objects.equals(this.zipCode, other.zipCode))
{
return false;
}
return true;
}

@Override
public String toString()
{
return "Address{" + "streetAddress1=" + streetAddress1 + ", streetAddress2="
+ streetAddress2 + ", municipality=" + municipality + ", state=" + state
+ ", zipCode=" + zipCode + '}';
}

}
Domain/Business Class Person.java

package dustin.examples.orikademo;

import java.util.Objects;

/**
* Person class.
*
* @author Dustin
*/
public class Person
{
private String lastName;
private String firstName;
private Address mailingAddress;
private Address residentialAddress;

public Person() {}

public Person(
final String newLastName,
final String newFirstName,
final Address newResidentialAddress,
final Address newMailingAddress)
{
this.lastName = newLastName;
this.firstName = newFirstName;
this.residentialAddress = newResidentialAddress;
this.mailingAddress = newMailingAddress;
}

public String getLastName()
{
return this.lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getFirstName()
{
return this.firstName;
}

public void setFirstName(String firstName)
{
this.firstName = firstName;
}

public Address getMailingAddress()
{
return this.mailingAddress;
}

public void setMailingAddress(Address mailingAddress)
{
this.mailingAddress = mailingAddress;
}

public Address getResidentialAddress()
{
return this.residentialAddress;
}

public void setResidentialAddress(Address residentialAddress)
{
this.residentialAddress = residentialAddress;
}

@Override
public int hashCode()
{
int hash = 3;
hash = 19 * hash + Objects.hashCode(this.lastName);
hash = 19 * hash + Objects.hashCode(this.firstName);
hash = 19 * hash + Objects.hashCode(this.mailingAddress);
hash = 19 * hash + Objects.hashCode(this.residentialAddress);
return hash;
}

@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final Person other = (Person) obj;
if (!Objects.equals(this.lastName, other.lastName))
{
return false;
}
if (!Objects.equals(this.firstName, other.firstName))
{
return false;
}
if (!Objects.equals(this.mailingAddress, other.mailingAddress))
{
return false;
}
if (!Objects.equals(this.residentialAddress, other.residentialAddress))
{
return false;
}
return true;
}

@Override
public String toString() {
return "Person{" + "lastName=" + lastName + ", firstName=" + firstName
+ ", mailingAddress=" + mailingAddress + ", residentialAddress="
+ residentialAddress + '}';
}

}

As was the case with Dozer, the classes being mapped need to have no-arguments constructors and "set" and "get" methods to support conversion in both directions without any special additional configuration. Also, as was the case with Dozer, Orika maps same-named fields automatically and makes it easy to configure the mapping of the exceptions (fields whose names don't match). The next code listing, for a class I call OrikaPersonConverter, demonstrates the instantiation and configuration of an Orika MapperFactory to map most fields by default and to map the fields with different names than each other ("municipality" and "city") through explicit mapping configuration. Once the MapperFactory is configured, copying from one object to another is easy and both directions are depicted in the methods copyPersonTypeFromPerson and copyPersonFromPersonType.

OrikaPersonConverter

package dustin.examples.orikademo;

import com.blogspot.marxsoftware.AddressType;
import com.blogspot.marxsoftware.PersonType;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;

/**
* Convert between instances of {@link com.blogspot.marxsoftware.PersonType}
* and {@link dustin.examples.orikademo.Person}.
*
* @author Dustin
*/
public class OrikaPersonConverter
{
/** Orika Mapper Facade. */
private final static MapperFacade mapper;

static
{
final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(Address.class, AddressType.class)
.field("municipality", "city")
.byDefault()
.register();
mapper = mapperFactory.getMapperFacade();
}

/** No-arguments constructor. */
public OrikaPersonConverter() {}

/**
* Provide an instance of {@link com.blogspot.marxsoftware.PersonType}
* that corresponds with provided {@link dustin.examples.orikademo.Person} as
* mapped by Dozer Mapper.
*
* @param person Instance of {@link dustin.examples.orikademo.Person} from which
* {@link com.blogspot.marxsoftware.PersonType} will be extracted.
* @return Instance of {@link com.blogspot.marxsoftware.PersonType} that
* is based on provided {@link dustin.examples.orikademo.Person} instance.
*/
public PersonType copyPersonTypeFromPerson(final Person person)
{
PersonType personType = mapper.map(person, PersonType.class);
return personType;
}

/**
* Provide an instance of {@link dustin.examples.orikademo.Person} that corresponds
* with the provided {@link com.blogspot.marxsoftware.PersonType} as
* mapped by Dozer Mapper.
*
* @param personType Instance of {@link com.blogspot.marxsoftware.PersonType}
* from which {@link dustin.examples.orikademo.Person} will be extracted.
* @return Instance of {@link dustin.examples.orikademo.Person} that is based on the
* provided {@link com.blogspot.marxsoftware.PersonType}.
*/
public Person copyPersonFromPersonType(final PersonType personType)
{
Person person = mapper.map(personType, Person.class);
return person;
}
}

As is the case with Dozer, the mapping between two classes is bidirectional and so only needs to be made once and will apply in copying from either object to the other.

Conclusion

Like Dozer, Orika offers much more customizability and flexibility than demonstrated in this post. However, for relatively simple mappings (which are very common with applications using JAXB-generated objects), Orika is very easy to use out of the box. A good resource for learning more about Orika is the Orika User Guide.

Thứ Bảy, 21 tháng 12, 2013

Closing 2013 with Core Java Posts

As 2013 starts drawing to a close, I have recently spotted several posts related to what I consider "core Java" knowledge appearing. This post is a listing of three posts and a forum with brief descriptions of these. I have two purposes in doing this: (1) to help others be aware of the existence of these fine posts and (2) to serve as a sort of glorified "bookmark" for myself when I want to find these again. These posts and forum were good enough to justify me taking the time to "print" (save) them to my hard drive as PDFs.

HotSpot JVM Flags

Kirk Pepperdine's "A Case Study of JVM HotSpot Flags" is an excellent resource on practical analysis and application of HotSpot's JVM flags. Pepperdine demonstrates use of the -XX:+PrintFlagsFinal flag (I have blogged on this as well) to "identify the redundant flags by figuring out what the default settings are" so that he could remove deprecated flags and flags that simply explicitly set to the default values they would have had anyway. Pepperdine then analyzes some of the flags remaining after removal of deprecated flags and removal of flags that simply restated the defaults. He analyzes how they interact and sometimes supersede one another. Pepperdine's concluding paragraph is well articulated advice:

I think it's fantastic that we have a technology that is so configurable, so flexible. But this flexibility is a double edged sword and one shouldn’t just jump blindly into using all of that configurability. One thing I do know is that your applications performance does depend on how it’s configured. Messing up on even one flag can have a detrimental effect on the performance of your application and getting it wrong is far easier than getting it right. And quite often, the JVM does get it right, right out of the box.
Remote Java Debugging

Adam Bien's post "What are the Options of Remote Debugging..." provides a short review of key options available for debugging a remote Java process such as an application server. In this post, he references Connection and Invocation Details for greater details but provides concise summaries of available transports (dt_socket and dt_shmem for socket and Windows shared memory access respectively), suspend=y versus suspend=n, and server=y versus server=n.

Java Standard Library/Language Wish List

The question "What is a feature that the Java Standard Library desperately needs?" was recently asked on Reddit. I found the replies (72 so far) to be interesting for a variety of reasons. There were a few examples of comment authors demonstrating their superior knowledge at the expense of others as has become increasingly common in online forums, but most of the comments were insightful and provided an interesting perspective on what Java developers would like to see added to the language (in a few cases) or to the SDK (more common in replies, probably because more closely aligned with the question). Answers included all of subset of Guava, all or subset of Apache Commons, deprecation and removal of largely unused features and libraries, and properties annotations or other mechanism to replace get/set methods (or Project Lombok).

I thought that Tillerino made an insightful comment that some Java developers might not realize regarding the "commons" libraries like Apache Commons (Lang specifically) and Guava. Tillerino stated:

commons-lang per definition provides those classes which are not in the Java API. Is is not uncommon that features which commons-lang provides appear in the Java API and are then removed from commons-lang. I have used a couple of other packages and while commons-lang is probably part of 90% of all projects, the others are just way to specialized to be a part of the Java API. It's important for Java developers to know which features are easily accessible through the commons, but I think the line has been drawn pretty well.

Recent examples of where Java SE has adopted and standardized popular libraries' features include Java 7's addition of the Objects class and Java 8's addition of the Optional class, both of which have obvious similarities with classes such as Guava's Objects class and Optional class. We see this in the Java EE world as well with one of the prime examples being the many new features of Java EE in recent years inspired by the Spring Framework.

This is a general principle that I have written about in my post Standardization: The Dangerous Relationship for Open Source. We developers should be "happy consumers" about this principle as open source seems to help refine best of breed ideas that the slower-moving standards organizations can adopt into the standards once those features' popularity and usefulness is proven. These open source produces' implementations allow us to use the feature before it is available in a standard implementation.

The RMI/Distributed Garbage Collection Connection

I appreciated Nikita Salnikov-Tarnovski's recent post RMI enforcing Full GC to run hourly for three reasons:

  1. The ultimate issue he addresses in this post is one I've run into before myself.
  2. I agree with his opening statement about the the bugs we are chasing down "are evolving and becoming meaner and uglier over time."
  3. Salnikov-Tarnovski not only states the solution to this mean bug, but he describes his process for isolating the bug and determining its cause.

The issue that Salnikov-Tarnovski ran into had to do with the "hourly Full GC taking place" due to the setting of the HotSpot RMI property called sun.rmi.dgc.server.gcInterval. The fix for bug JDK-6200091 ("RMI gcInterval is too short by default") lengthened the settings of sun.rmi.dgc.server.gcInterval and sun.rmi.dgc.server.gcInterval to one hour in Java SE 6. For reference, other posts of interest related to regular periodic distributed garbage collection include How we solved – GC every 1 minute on Tomcat, Analyze GC logs for Sun Hotspots, JVM 6, Unexplained System.gc() calls due to Remote Method Invocation (RMI) or explict garbage collections, and If you don't do this JBoss will run really slowly.

I like that Salnikov-Tarnovski documented his steps in resolving the issue rather than simply stating the resolution. Although the resolution in this case was searching with Google search engine for someone else's account of how to resolve the issue, the several steps tried before that can be illustrative to others of how to narrow down a problem and hone in on a defect. One of the greatest questions I've been asked on this blog was when a person left a comment asking how I found the fix that I had documented in that post. By the time I posted and read this comment, I had forgotten the steps I had used to resolve that particularly tricky issue and so could not really help.

In Salnikov-Tarnovski's case in this post, about all that was provided was "sometimes the system is slow" (I'm sure many of us have been on the receiving end of that more than once). The steps used to diagnose the issue included monitoring response times to see a regular pattern, ruling out the usual suspects in cases like these, increasing logging output, and finally resorting to Google.

Conclusion

There are thousands of new posts each week detailing a wide variety of new technologies and tools for the software developer. These are valuable posts and help all of us to learn new things, but I also appreciate posts and forums that provide focus on "core concepts." While core concepts may not be as exciting or flashy as new things, core concepts help us to better deliver and manage software for our customers' benefit.

Thứ Năm, 19 tháng 12, 2013

$5 Packt Publishing Electronic Books and Videos

From now through 3 January 2014, Packt Publishing is offering any of their extensive collection of books (they estimate the number as over 1700) in electronic format for $5 (USD) each (deal also applies to videos available on their site).

This is similar to the year-end deal they offered last year and additional details can be found at $5 Book Bonanza page.

Although I have received free copies of Packt Publishing books that I have reviewed in the past or as compensation for reviewing a Packt Publishing book, I am posting about this $5 eBook Bonanza primarily because I think it may be of value to software developers. Browsing the catalog can make one aware of the wide variety of languages, toolkits, frameworks, and libraries that are out there and, for $5, it might be worth it to purchase a book to find out more about a potentially interesting topic.

Here are some of my prior reviews of Packt Publishing books:

I am currently reading Java EE 7 First Look and plan to post my review of it before the calendar year is ended (and before the $5 eBook Bonanza offer expires).

There are a few other Packt Publishing books that look particularly interesting to me such as Mastering Web Application Development with AngularJS, Gradle Effective Implementation Guide, Java EE 7 Developer Handbook, Responsive Web Design with HTML5 and CSS3, HTML5 and CSS3 Responsive Web Design Cookbook, Getting Started with Meteor.js JavaScript Framework, Linux Utilities Cookbook, and LaTeX Beginner's Guide.

Thứ Tư, 18 tháng 12, 2013

Book Review: HTML5 Data and Services Cookbook

Packt Publishing recently published HTML5 Data and Services Cookbook by Gorgi Kosev and Mite Mitreski. The subtitle of the book is: "Over one hundred website building recipes utilizing all the modern HTML5 features and techniques!" I accepted an invitation to review this book and was provided with an electronic copy of that book that I am reviewing in this post.

HTML5 Data and Services Cookbook is a collection of "recipes" organized in categories such as text, graphics, animation, input, data storage, validation, and server communication. The recipes cover not only HTML, but also JavaScript and CSS. This is not too surprising given that HTML5 is really much more than simply HTML.

Preface (Target Audience and Prerequisites)

The Preface provides a brief overview of each of the book's 12 chapters and 2 appendices. The Preface also describes what readers should have available when reading the book such as "a modern browser" such as "Firefox, Chrome, Safari, Opera, or Internet Explorer 9," a text editor, and an Internet connection. The authors also mention in the Preface that node.js is used for examples in later chapters of the book.

The "Who this book is for" section of the Preface describes the audience targeted by the authors:

This book is for programmers who already have used JavaScript in one way or the other. It's for people who work with a lot of backend code, and want to get up to speed with the world of HTML5 and JavaScript. It's for people who have used copy/paste to patch up a part of a page and want to know more about how things work in the background. It's for JavaScript developers who would like to update their knowledge with new techniques and capabilities made possible with HTML5. The book is for both beginners and seasoned developers, assuming that you will have some experience in HTML, JavaScript, and jQuery already, but not necessary [sic] an in-depth knowledge.
Chapter 1: Display of Textual Data

The first sentence of the initial chapter of HTML5 Data and Services Cookbook introduces the chapter with the statement, "The most common task related to web application development is the displaying of text." The authors use this chapter to describe how to work with text presentation (including presentation of numeric text and dates as well as alternate text formats). I like the fact that the authors include some iterative and functional examples and describe differences between them including disadvantages and advantages of each. The authors also demonstrate how to add a function to a standard JavaScript object but at the same time explain why this is not generally a good idea. These are examples of how the book does more than simply show how to accomplish a certain task.

Chapter 2: Display of Graphical Data

Chapter 2 of HTML5 Data and Services Cookbook looks at "displaying graphical data using various JavaScript libraries that are based on modern HTML5 standards." Presentation of several types of charts (line, bar, pie, area, and bubble) are demonstrated in the recipes of Chapter 2 and these make use of the Flot and D3 charting libraries. Scalable Vector Graphics (SVG) technology is also applied in this chapter.

The second chapter also includes detailed recipes on creating simple mapping applications, creating and using a gauge jQuery plugin, and creating an LED scoreboard. The last recipe goes into quite a bit of detail regarding font snf introduces terms such as Web Open Font Format (WOFF), TrueType Font (TTF), OpenType Font (OTF), and Flash of Unstyled Text (FOUT).

Chapter 3: Animated Data Display

The third chapter of HTML5 Data and Services Cookbook covers "some common ways of doing animated data visualizations with minor interactions." D3 and SVG figure prominently in this chapter and one of the recipes is devoted to Web Notifications.

Chapter 4: Using HTML5 Input Components

Chapter 4 provides recipes related to HTML form input fields. It includes standard input form material (placeholder text, HTML5 Date Pickers, Color Pickers, Range Pickers, etc.) as well as information on less commonly discussed input mechanisms such as HTML Speech/Web Speech API. This chapter also features recipes on the Geolocation API Specification, File API, and Drag and Drop.

Chapter 5: Custom Input Components

The fifth chapter of HTML5 Data and Services Cookbook builds on the fourth chapter with recipes that demonstrate how to extend the standard HTML input elements. Recipes in this chapter outline use of contentEditable (and browser support for it), Embedded JavaScript Templates (including John Resig's JavaScript Micro-Templating), and OpenStreetMap Nominator as they describe creation of rich content elements, drop-down menus, custom dialogs, input autocompletion (applying jQueryUI and Chosen), and map-related applications.

Chapter 6: Data Validation

Chapter 6 of HTML5 Data and Services Cookbook covers data validation and looks at "new mechanisms provided mostly for client-side checks by HTML5 as well as how to tackle some common problems." Recipes in this chapter cover validating form fields, length of text fields, spelling and grammar checks, numeric range checks, calculating password strength, and validating United States zip codes. The examples also include application of box shadows, the required attribute of input tags, and references to the client-side form validation and constraint validation API specifications. This chapter also contains the first recipe requiring use of node.js.

Chapter 7: Data Serialization

HTML5 Data and Services Cookbook's seventh chapter introduces its recipes with the statement, "One of the basic concepts of data storage and transmission is serialization" and then the chapter's recipes demonstrate approaches for saving data, reading data, and preparing data for transmission. Binary data (ArrayBuffer), XML data, and, of course, JSON data each get attention in one or more recipes of this chapter. Serialization inherently involves encoding and decoding, so it's not surprising that much attention is paid to encoding and decoding in JavaScript.

Chapter 7 reminds the reader that while "JSON is language independent format," it still "is JavaScript." I like that the authors point out that while one could use eval to evaluate JSON data as a subset of JavaScript, its use is "something we should avoid in most cases." The authors instead recommend using JavaScript 1.7's JSON.parse function and, for browsers too old to support that, recommend the fallbacks of either JSON2 or JSON3.

Chapter 7 includes a demonstration of CanvasRenderingContext2D in recipes in which binary data needs to be created. The chapter also mentions use of JavaScript typed arrays. One of the recipes in Chapter 7 introduces Keyhole Markup Language (KML). As would be expected from a chapter on serialization in HTML5, recipes in this seventh chapter use jQuery.serialize(), jQuery.serializeArray(), DOMParser, and XMLSerializer.

Before reading Chapter 7 of HTML5 Data and Services Cookbook, I was not aware of JXON (lossless JavaScript XML Object Notation). The authors introduce JXON and describe it as "the API related to creation and use of XML documents in JavaScript" that "defines a convention for a two-way conversion between JSON and XML." The recipe that introduces JXON also introduces something with which I'm much more familiar: XPath (XML Path Language).

Chapter 8: Communicating with Servers

Although Ajax is used throughout recipes of earlier chapters in HTML5 Data and Services Cookbook, it is Chapter 8 that more formally introduces the concept of Asynchronous JavaScript and XML and XMLHttpRequest. Just as I thought it helpful when JavaScript and JSON Essentials demonstrated applying Ajax implementation directly in JavaScript before using the easier JQuery-based approach, the authors of HTML5 Data and Services Cookbook intentionally show direct JavaScript implementation of asynchronous communication with the (node.js-based) server in early recipes of Chapter 8. The authors explicitly state their very good reasons for this decision: "We strongly believe that jQuery simplifies the DOM API, but it is not always available to us, and additionally, we need have to know the underlying code behind asynchronous data transfer in order to fully grasp how applications work."

I was pleased to see coverage of XMLHttpRequest Level 2 in Chapter 8. The authors succinctly describe the major differences and improvements of this version and discuss browser support for it. Other topics covered in this chapter include Cross-Origin Resource Sharing, Semantic Versioning, JSONP (JSON with Padding), building XML with xmlbuilder-js, and leveraging WebSockets with dnode.

Chapter 8 includes recipes addressing security issues. The concepts of Secure Sockets Layer (SSL) and Transport Layer Security (TLS) are discussed and the OpenSSL Project and Socket.io are used. There is mention of the Open Web Application Security Project (OWASP) and its HTML5 Security Cheat Sheet.

Chapter 9: Client-side Templates

Chapter 9 of HTML5 Data and Services Cookbook addresses "the shift in web apps from using server-side HTML rendering to client-side HTML rendering" that is attributed to many of today's target platforms not supporting HTML. The recipes in this chapter take turns using one of three "popular client-side template languages": EJS, Handlebars, and Jade.

Chapter 10: Data Binding Frameworks

When I was browsing the table of contents before reading HTML5 Data and Services Cookbook, one of the chapters I was looking most forward to reading was Chapter 10 because it covers two trendy JavaScript frameworks that I only had minimal knowledge (not much more than awareness) of: AngularJS and Meteor. The authors open the chapter contrasting these two frameworks and one of the most obvious differences that they highlight is that AngularJS "provides client-side bindings and can work with any server-side stack" while Meteor is "a complete framework and platform that covers both the client and the server side." The section on Meteor reproduces and references the "Seven Principles of Meteor" that is available on the main Meteor documentation page.

MongoDB is referenced and used in multiple recipes in Chapter 10. Other introduced concepts include EJSON, reactive programming, and Meteor's spiderable.

Chapter 11: Data Storage

Chapter 11 of HTML5 Data and Services Cookbook "covers some of the features that are related to HTML5 and are about data storage." The recipes in this chapter cover Data URI, Web Storage (Local Storage), IndexedDB, Web SQL Database, Quota Management API, and History API.

I was particularly intrigued by the number of fallback mechanisms for Web Storage listed in HTML5 Data and Services Cookbook. One of the referenced fallbacks is actually two choices of code implementations and are based on cookies for browsers not supporting Web Storage. Other referenced fallbacks for when Web Storage is not supported include implementations based on Google Gears and Flash.

The authors' recipes include a good explanation of why they cannot currently recommend use of IndexedDB and instead recommend a couple of alternatives.

Chapter 12: Multimedia

The final non-appendix chapter of HTML5 Data and Services Cookbook, Chapter 12, covers on the the areas that is most wanting in HTML5 browser compliance: multimedia support. One of the issues making this so difficult is the plethora of formats available. Indeed, in the first recipe of this chapter, the authors refer the reader to media.io to covert files into other formats for use in the recipe. Before displaying a handy table detailing the "rough state of the browser format support using Windows as operating system," the authors make this important observation about working with multimedia in HTML5: "With the current state some browsers support certain format but others do not. If we want to have support in all modern browsers then we supply the option to have multiple sources." After two decades of development for the Internet, browser incompatibilities remain an influential force.

Chapter 12 introduces The Internet Archive (home of the Wayback Machine) as a source of publicly available videos to use for a recipe on video handling. I liked the chapter's introduction of MediaElement.js as a simple fallback mechanism for browsers not supporting the media elements or not supporting certain media formats.

WebVTT (The Web Video Text Tracks Format) is also covered in Chapter 12 along with a reference to Live WebVTT Validator and the Timed Text Markup Language (TTML). The chapter briefly references the Web Audio API before moving to coverage of converting text to speech in HTML5 using emscripten, speakjs, and eSpeak.

The Appendices

HTML5 Data and Services Cookbook includes two appendices with Appendix A devoted to "Installing Node.js and Using npm" (node package manager) and Appendix B covering "Community and Resources." Appendix A is particularly useful to and important for readers who wish to run the many node.js-based recipes in HTML5 Data and Services Cookbook but who do not already have Node.js running.

Appendix B covers WHAT Working Group (WHATWG), Worldwide Web Consortium (W3C), Mozilla Developer Network (MDN) [source of many of the links I embedded in this post], HTML5 Rocks, Dive Into HTML5, HTML5 Test, and QuirksMode Compatibility Master Table.

General Observations
  • HTML5 Data and Services Cookbook references and demonstrates use of several tools and libraries related to HTML5 such as jQuery (1.8.2), jQuery plugins (timeago, DataTables, Validation), node.js (including modules node-restify and FileSystem), big.js, Moment.js, MathJax, ASCIIMathML.js, google-code-prettify, Markdown (markdown-js), Flot, D3.js, Leaflet, OpenStreetMap (including Nominatim), FontSquirrel, google.com/fonts, Rickshaw, GeoJSON, TopoJSON, html5-slider, TinyMCE, Embedded JavaScript Templates, John Resig's JavaScript Micro-Templating, Handlebars, jade, jQueryUI, Chosen, HTML5Pattern, Webshims lib, yepnope.js, Base64.js, hashify.me, form2js, xmlbuilder-js, Connect, OpenSSL, Socket.io, dnode, sessionstorage, Storage polyfill, Storage Compatibility, PouchDB, db.js, history.js, path.js, Abaroids, media.io, AreWePlayingYet?, MediaElement.js, Live WebVTT Validator, Captionator.js, emscripten, speakjs. Many of these referenced products are available via cdnjs.
  • HTML5 Data and Services Cookbook contains recipes using and covering several different approaches and tactics commonly used in JavaScript and HTML5 such as Ajax, JSON, Comet, and WebSockets.
  • HTML5 Data and Services Cookbook not only has recipes showing how to accomplish certain specific tasks with HTML5 technologies, but it also makes a lot of nice points along the way about things to do and not do with HTML5 technologies.
  • One of the most frustrating aspects of web development has always been differing support of alleged standards by different web browsers
    • HTML5 Data and Services Cookbook often calls out features not supported in certain browsers at the time of its writing.
    • Chapter 4 lists two tools that can be used to help determine updated browser support for a particular capability: http://caniuse.com/ and http://html5please.com/.
    • Compatibility Master Table is referenced in Chapter 6.
    • I have also used Modernizr for this.
    • The authors also provide appropriate fallbacks, shims, and polyfills to use in many of the recipes when the newer covered feature may not work in some browsers.
  • The code listings in the electronic version of HTML5 Data and Services Cookbook are differentiated from regular prose by a fixed-width font being used for code. Other than the different font, there are no differences between code listings and prose. It would be easier to read the large amount of code in this book if it had color coded syntax and was offset with a border or other visual separator. However, the code can be copied-and-pasted easily in this form.
  • Although there are many code listings in this book, not all code used in the recipes is directly available in the book. Some listings require code to be downloaded from the book's companion web site and many of the code listings depend upon availability of third-party libraries and frameworks.
  • An advantage of reviewing the electronic version of HTML5 Data and Services Cookbook is that many of the screen snapshots of HTML5 applications are reproduced in full color.
  • I really liked the wide breadth of realistic but small examples contained in the recipes of HTML5 Data and Services Cookbook. The "domain" modeled by these recipes introduced some things new to me and in some cases were as interesting as the code examples.
  • I expected to learn some new techniques related to HTML5 from reading this book and was not disappointed. However, I was pleasantly surprised to learn some interesting minor pieces of information with no specific relationship to HTML5 during my reading. These include things like the existence of the United States postal zip codes in CSV format and how/why the UTC acronym (formerly GMT) was selected. These are just a few examples of many incidental things I picked up from reading this book.
  • Most of HTML5 Data and Services Cookbook is highly readable and flows fairly well. However, there are a significant number of strange wording constructs and typos. One example comes from Chapter 7: "JSON is extremely simple to use than JavaScript; there are lot of REST services already out there that use XML." I have my suspicions of what this means (and wrote my interpretation when I filed this errata), but I cannot be sure what the intent was. Most of the cases of strange wording constructs are easier to figure out the intent for (many cases were strangely worded because adjectives were used where adverbs should have been used instead or vice versa), but the relatively large number of these strange wordings make me think HTML5 Data and Services Cookbook would have benefited from another and more thorough English grammar editing process. I'd describe the overall editing quality of HTML5 Data and Services Cookbook to be equivalent to a well-written blog post, but I typically expect even a well-written blog post to not be able to compare to a book in terms of editorial quality.
Conclusion

I really liked HTML5 Data and Services Cookbook. I learned several new things, reinforced some things I had previously learned, and had several ideas come to mind as I read the book. I particularly liked the book's approach of making liberal use of freely available open source frameworks and toolkits to do some of the recipes' heavy lifting. This approach made the recipes easier to implement and read and introduced me to some tools and frameworks I was not previously aware of. I also appreciated that most recipes covered fallback mechanisms that are available when the newer HTML5 feature is not supported in a particular browser. The many side notes and special emphases in HTML5 Data and Service Cookbook added tremendous value to me as I read the book.

The biggest downsides of HTML5 Data and Services Cookbook were related to presentation rather than to content. The code listings were not as easy-to-read as I would have liked and some of the grammar and sentence structure could have used more editing.

I strongly recommend HTML5 Data and Services Cookbook, especially for those who have similar HTML5 experience to mine. I've been away from it for a couple of years and this book helped me catch up on some of the latest happenings in the community, the specifications, the technologies, and the available libraries and toolkits. It reminded me of the excitement (new technologies and specifications that affect daily online and mobile experience) and the frustration (differing browser support for features, incompatibilities between browsers, and sometimes slowly evolving standards) of developing with HTML5.

Thứ Hai, 16 tháng 12, 2013

Searching Subversion Logs with Groovy

There are times when I want to quickly search a Subversion repository by author, by range of revisions, and/or by commit messages. Krzysztof Kotowicz has posted the blog post Grep Subversion log messages with svn-grep that introduces svn-grep, a bash script making use of the command line XML toolkit called xmlstarlet (xmlstarlet is also available on Windows). This is a pretty useful script in and of itself, but it gave me an idea for a Groovy-based script that could run on multiple (all JVM-supported) platforms.

searchSvnLog.groovy

#!/usr/bin/env groovy
//
// searchSvnLog.groovy
//
def cli = new CliBuilder(
usage: 'searchSvnLog.groovy -r <revision1> -p <revision2> -a <author> -s <stringInMessage>')
import org.apache.commons.cli.Option
cli.with
{
h(longOpt: 'help', 'Usage Information', required: false)
r(longOpt: 'revision1', 'First SVN Revision', args: 1, required: false)
p(longOpt: 'revision2', 'Last SVN Revision', args: 1, required: false)
a(longOpt: 'author', 'Revision Author', args: 1, required: false)
s(longOpt: 'search', 'Search String', args: 1, required: false)
t(longOpt: 'target', 'SVN target directory/URL', args: 1, required: true)
}
def opt = cli.parse(args)

if (!opt) return
if (opt.h) cli.usage()

Integer revision1 = opt.r ? (opt.r as int) : null
Integer revision2 = opt.p ? (opt.p as int) : null
if (revision1 != null && revision2 != null && revision1 > revision2)
{
println "It makes no sense to search for revisions ${revision1} through ${revision2}."
System.exit(-1)
}
String author = opt.a ? (opt.a as String) : null
String search = opt.s ? (opt.s as String) : null
String logTarget = opt.t

String command = "svn log -r ${revision1 ?: 1} ${revision2 ?: 'HEAD'} ${logTarget} --xml"
def proc = command.execute()
StringBuilder standard = new StringBuilder()
StringBuilder error = new StringBuilder()
proc.waitForProcessOutput(standard, error)
def returnedCode = proc.exitValue()
if (returnedCode != 0)
{
println "ERROR: Returned code ${returnedCode}"
}

def xmlLogOutput = standard.toString()
def log = new XmlSlurper().parseText(xmlLogOutput)
def logEntries = new TreeMap<Integer, LogEntry>()
log.logentry.each
{ svnLogEntry ->
Integer logRevision = Integer.valueOf(svnLogEntry.@revision as String)
String message = svnLogEntry.msg as String
String entryAuthor = svnLogEntry.author as String
if ( (!revision1 || revision1 <= logRevision)
&& (!revision2 || revision2 >= logRevision)
&& (!author || author == entryAuthor)
&& (!search || message.toLowerCase().contains(search.toLowerCase()))
)
{
def logEntry =
new LogEntry(logRevision, svnLogEntry.author as String,
svnLogEntry.date as String, message)
logEntries.put(logRevision, logEntry)
}
}
logEntries.each
{ logEntryRevisionId, logEntry ->
println "${logEntryRevisionId} : ${logEntry.author}/${logEntry.date} : ${logEntry.message}"
}

One thing that makes this script much easier to write is the ability of Subversion's log command to write its output in XML format with the --xml flag. Although XML has been the subject of significant criticism in recent years, one of the things I've liked about its availability is the widespread tool support for writing and reading XML. Subversion's ability to write certain types of output in XML is a good example of this. Without XML, the script would have required custom parsing code to be written to parse the non-standard SVN log output. Because Subversion supports writing to the standard XML format for its output, any XML-aware tool can read it. In this case, I leveraged Groovy's incredibly easy XML slurping (XML parsing) capability.

The script also uses Groovy's enhanced (GDK) Process class as I briefly described in my recent post Sublime Simplicity of Scripting with Groovy.

Groovy's built-in command-line support (CliBuilder) is used in the script to accept parameters for narrowing the search (such as applicable revisions, authors who committed, or strings to search the commit comments for). The one required parameter is the "target" which can be a file, directory, or URL.

The script references a Groovy class called LogEntry and the code listing for that class is shown next.

LogEntry.groovy

@groovy.transform.Canonical
class LogEntry
{
int revision
String author
String date
String message
}

That simple-looking LogEntry class is much more powerful than it might first appear. Because it's Groovy, there are automatically setter/getter methods available for the four attributes. Thanks to the @Canonical annotation, it also supports a constructor, equals, hashCode, and toString methods. In other words, this class of under ten lines total has accessor and mutator methods as well as common class methods overridden appropriately for it.

Conclusion

Groovy offers numerous features to make script writing easier. In this post, I used an example of "searching" Subversion commits via the Subversion log command (and its --xml option) to demonstrate some of these useful Groovy scripting features (command line parameter parsing, native operating system integration, and easy XML parsing). Along the way, some of Groovy's nice syntax advantages (closures, dynamic typing, GString value placeholders) were also used.

Thứ Bảy, 14 tháng 12, 2013

Book Review: JavaScript and JSON Essentials

Packt Publishing recently invited me to review JavaScript and JSON Essentials. They provided this book authored by Sai Srinivas Sriparasa in electronic format. Note that Sriparasa has also written a Packt Publishing article called Getting Started with JSON.

The Preface of JavaScript and JSON Essentials describes optional prerequisites for readers of the book: "It ... would be good to have some knowledge about HTML and JavaScript. Some familiarity with server-side languages such as PHP, C#, or Python would be preferred, but not required."

Chapter 1: JavaScript Basics

The initial chapter of JavaScript and JSON Essentials provides history and background on JavaScript. It describes JavaScript's relationship to HTML, CSS, and web browsers. It then moves to coverage of JavaScript syntax basics.

Chapter 2: Getting Started with JSON

The second chapter of JavaScript and JSON Essentials introduces JSON (JavaScript Object Notation) with a very brief historical background and emphasizes that "JSON is a format and not a language." The author wastes no time in touting some of the basics and virtues of JSON: "JSON is derived from JavaScript and bears a close resemblance to JavaScript objects, but it is not dependent on JavaScript. JSON is language-independent, and support for the JSON data format is available in all the popular languages." This chapter also provides a very basic comparison of JSON to XML as data interchange formats.

JSON's MIME representations, a sample Hello World example, and a comparison of JSON to JavaScript objects are all provided in the second chapter.

The second chapter includes other aspects you'd expect in a chapter introducing a format. For example, it describes JSON data types and programming languages that support JSON (with emphasis on PHP and Python).

Chapter 3: Working with Real-time JSON

With the basics of JavaScript covered in Chapter 1 and the basics of JSON covered in Chapter 2, Chapter 3 covers using JSON in "real-world applications." There is significant discussion and examples in this chapter of parsing and modifying static JSON with JavaScript.

Up until this point, the author used JavaScript's alert() to quickly and easily provide output from JavaScript code. I was pleased to see in this third chapter that the author explains the availability of console.log in modern browsers and explains some of the reasons console.log is preferred over alert().

Chapter 4: AJAX Calls with JSON Data

Chapter 4 moves JavaScript and JSON Essentials's focus from working with relatively static JSON data to working with dynamic JSON data. The chapter begins with a brief history of web application development and the move from synchronous communication to increasingly asynchronous communication (IFrame, XML HTTP ActiveX control, XMLHttpRequest). Sriparasa describes how the 'X' in AJAX (now commonly spelled Ajax) was originally for XML but that now JSON is commonly used instead of XML.

The fourth chapter spends considerable time explaining Ajax and demonstrating use of JSON with Ajax. The descriptive text and screen snapshots cover use of JSON with Ajax using straight JavaScript before demonstrating how to do this with jQuery (with emphasis on using jQuery.getJSON()). I really like that the author demonstrated implementing Ajax with direct JavaScript before introducing jQuery because I think it is advantageous to understand the underlying fundamental principles behind the libraries we use. This would probably be overkill in a book solely devoted to JSON, but seemed well-placed in a book that covers JavaScript's "essentials."

To illustrate the Ajax/JSON examples, Sriparasa first leads the reader through the "setting up Apache and PHP to develop server-side programs on a Linux machine" and "running a .NET-powered web application on Windows." As part of this, installation and use of the LAMP stack is covered and there are numerous screen snapshots of Visual Studio being used.

Chapter 5: Cross-domain Asynchronous Requests

JavaScript and JSON Essentials's fifth chapter begins with a deeper dive into the basic HTTP methods of GET and POST. The chapter mentions web debug tool Fiddler before going on to an example of POST-ing JSON data using jQuery.ajax() method, PHP, and MySQL. Another example in the fifth chapter leverages Reddit JSON-based APIs. JSONP (JSON with Padding) is introduced in Chapter 5 as part of the Reddit API-based example.

Chapter 6: Building the Carousel Application

The sixth chapter of JavaScript and JSON Essentials builds a "rotating notification board application" (a "photo gallery" style application) upon the concepts covered in the previous five chapters. The example is based on HTML, jQuery, and jQuery Cycle and spends a number of pages on applying jQuery Cycle.

Chapter 7: Alternate Implementations of JSON

The first six chapters of JavaScript and JSON focused on using JSON with HTML and JavaScript and on using JSON as a data interchange format, but the seventh chapter shifts focus to using JSON with other languages and in ways other than data interchange. Specifically, the author talks about using JSON capabilities of Composer (PHP) and Node Packaged Modules (Node.js) for dependency management, using JSON as a language-independent (Python and PHP get emphasized here) way to store application configuration data, and how JSON compares to YAML (described as "another software language-agnostic data interchange format").

Chapter 8: Debugging JSON

The final chapter of JavaScript and JSON Essentials, Chapter 8, looks at "different ways ... we can debug, validate, and format JSON." The chapter begins with a very high-level introduction to web browser support for diagnosing what's happening with JSON in the request or response and then delves more deeply into using Firebug with the Firefox browser and using the developer tools associated with Google Chrome, Safari, and Internet Explorer (F12) browsers.

Chapter 8's coverage of JSON validation tools looks at JSONLint. The author talso introduces JSON Editor Online as a JSON formatting tool. He explains some advantages of both tools and shows screen snapshots of each in use. Chapter 8's Summary's first paragraph summarizes the chapter and its other paragraph summarizes the book. One sentence from this book summary paragraph nicely summarizes what I feel is the ultimately accomplished objective of JavaScript and JSON Essentials: "JavaScript and JSON Essentials [is] targeted to provide you with an in-depth insight of how data can be stored and transferred in the JSON data format."

General Observations
  • JavaScript and JSON Essentials seems best suited for developers with only very basic JavaScript background or who have not used JavaScript regularly since the mid-2000s because its focus is on use of JSON, Ajax, and other JavaScript related technologies that have been more prevalent since around 2005. There is an introductory chapter on JavaScript, but a person with no experience with JavaScript might want to prepare for this book by also reviewing online references such as Introduction to JavaScript for the total beginner and A re-introduction to JavaScript (JS Tutorial).
  • JavaScript and JSON Essentials is a quick read. With eight chapters, just over 100 pages, and numerous screen snapshots (code and tools), this book is not overly long and does not waste a lot of space on unnecessary details.
  • JavaScript and JSON Essentials uses numerous color screen snapshots to demonstrate code and applications. There are aesthetic advantages (such as color coded syntax and better visual separation of code from prose) with these images and they do provide a sense of realistic applicability, but one downside is that image snapshots of code cannot be copied and pasted. Fortunately, all code examples associated with this book are available for download.
  • The screen snapshots of code listings have black and dark gray backgrounds with light fonts for the text. This works well for the electronic version of the book I reviewed, but it does not seem like it would work well nearly as well in a printed book with such dark backgrounds.
  • There are some minor editing-related issues such as cases where one sentence that would fit on a single line in the book is spread over multiple lines with only a few words on each of the lines. There are only two or three of these cases and most of the prose is readable.
Conclusion

JavaScript and JSON Essentials introduces Java Script, JSON, and Ajax and some of the most important aspects of each along with how to use them together.