Pages

Wednesday, April 04, 2012

Sorting Arrays of Structs in ColdFusion

This is a bit of a follow-up on my previous post Querying ColdFusion Data Structures where I explain how to use JXPath to query/filter ColdFusion structs. Querying is only ½ the picture though - we also need to be able to sort data.

Using arraySort is fine, if your array has nothing but strings or numbers, but it won't do for an array of structs, and is limited to sorting text and numbers, not complex objects.

Let us take for example an array of employee structs, something like this:

employees:[
 {firstName:'John',lastName:'Doe'},
 {firstName:'Ben',lastName:'Anderson'}
 {firstName:'Jane',lastName:'Doe'},
]
We want to sort based on lastname and then firstname. In SQL this very simple:
SELECT * from EMPLOYEES ORDER BY lastName,firstName
We could implement our own sort in ColdFusion, looping, and storing the sorted data in a new array, sure. But there's a way to do this using Java that requires a lot less code. We can leverage the java.util.Collections object, which has a sort() method that allows you to specify a java.util.List to sort, and a custom Comparator object.

import java.util.Comparator;
import java.util.Map;

public class EmployeeComparator implements Comparator {
   public int compare(Object ob1, Object ob2) {
      Map m1 = (Map)ob1;
      Map m2 = (Map)ob2;

      (String)m1LastName = (String)m1.get("LASTNAME");
      (String)m2LastName = (String)m2.get("LASTNAME");

      if (m1LastName.compareTo(m2LastName) == 0) {
         String m1FirstName = (String)m1.get("FIRSTNAME");
         String m2FirstName = (String)m2.get("FIRSTNAME");
         return m1FirstName.compareTo(m2FirstName);
      }
      else {
         return m1LastName.compareTo(m2LastName);
      }
   }
}
This works fine but there's a catch. We have to know our data structure format ahead of time, compile and deploy the class to the coldfusion server. It's not generic. We can implement a coldfusion.runtime.Struct specific comparator that will take a list of sort fields instead, which will take care of most of our needs with a single class. The only caveat is that those sort fields need to implement Comparable (which most of the basic types do).
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import coldfusion.runtime.Struct;

public class StructComparator implements Comparator<Struct> {
 private final List<String> sortFields;
 
 public StructComparator(String fields) {
  sortFields = Arrays.asList(fields.split(","));
 }
 
 @SuppressWarnings({ "rawtypes", "unchecked" })
 public int compare(Struct struct1, Struct struct2) {
  
  for (String field : sortFields) {
   Comparable x = (Comparable)struct1.get(field);
   Comparable y = (Comparable)struct2.get(field);
   if (x.compareTo(y) != 0) {
    return x.compareTo(y);
   }
  }

  return 0;
 } 
}
This sorts all the fields passed in, in order, in the same direction. And since we're using Struct.get() to pull values, we don't have to worry about case sensitivity either. I'll leave it as an exercise to the reader to enhance this class to allow specifying the sort direction and other options. Happy coding!

Wednesday, February 02, 2011

Querying ColdFusion Data Structures

Often times one needs to be able to pull a subset of data from an array of structs, and based on some value in the struct, perform an operation on it. For example, we have an array of structs that contains employees. We want to perform an operation, say if the employee's name is Bob. The normal way to do this would be like so:
<cfloop array="#employees#" index="employee">
   <cfif employee.name is "Bob">
      <!--- do something --->
   </cfif>
</cfloop>
While this doesn't look too bad. But it can get complicated, especially if we have several different conditions to check for, or if we have to deal with deeply nested structures (arrays of structs containing arrays of structs...). I've seen folks repeat the loop multiple times to keep the logic manageable, and I've seen folks convert their arrays of structs into query objects, and then use query of queries to perform these types of operations. I've even been guilty of such things myself.

Enter XPath. XPath is designed to let you query XML documents. In the example above, to get all the employee's named Bob from an XML file, you would write an XPath query that looks like this: "/employees[@name='Bob']", which would give you a reference to all the employee entries. JXPath, a part of the Apache Commons project, implements an XPath query engine, but rather than querying an XML document, it's designed to allow you to query java objects (which coldfusion objects are). Using JXPath, we can get a reference only to those entries that match our criteria, and loop over them.

To get started I just grabbed the latest JXPath .zip file from the website, and dropped the commons-jxpath.jar in ColdFusion's lib directory, and restarted services. The only real trick to this, is that CF stores just about everything in uppercase, except struct members created using array style notation (e.g., mystruct["field"] instead of mystruct.field) — these are stored in their original case. The XPath queries are case sensitive, so if you create your struct members in this manner, watch out.
<!--- create a context from whatever scope you're using (can be a struct) --->
<cfset variables.context = createObject("java","org.apache.commons.jxpath.JXPathContext").newContext(variables) />

<!--- get an iterator over the elements that match your xpath query ---->
<cfset variables.EmpIterator = context.iterate("/EMPLOYEES[@NAME='Bob']") />

<!--- now, do something with those elements --->
<cfloop condition="#variables.EmpIterator.hasNext()#">
 <cfset employee =variables.EmpIterator.next() />
</cfloop>

Saturday, January 29, 2011

Recording Skype Calls on a Mac for Free

So, you want to record a call on Skype for some reason. And you're on a mac. First, check the legality in your area. I am not a lawyer. Some states require all people on a call to give consent to the recording, some states only require one person to give consent. In general, always ask permission.

Now, you could choose a commercial solution, which would probably be easier if it's something you're going to use a lot. There are cheap options, and expensive options. There are options out there for also recording the video. But for once in a blue moon, and at least for the audio portion, it can be done for free.

Before going any further, I have to give a shout out to Rodd Lucier, who wrote a similar blog post back in 2008, and which helped get me started on this.

Here's what you'll need:


1) In Skype, set the output to SoundFlower (16ch). Skype will use channels 1 and 2, for stereo audio received on the call. This will only give you 1/2 of the conversation though - your end won't be recorded.


2) In SoundFlower bed, go to the SoundFlower (16ch) menu, go to buffer size, and set it to 2048. This increases the buffer size so the recording runs smoothly. In SoundFlower bed, select your headset under SoundFlower (16ch). This will route the audio going out over the SoundFlower device, back to your headset so that you can hear.

3) Fire up LineIn. Select your headset as the input device. For the output device, select SoundFlower 16ch. Open up the advanced settings. Increase the input device settings to a buffer of 1024. For the output device, set the left channel to 3, the right channel to 4, and bump the buffering up to 4096. Hit ok on the Advanced Device Options, and hit the "Pass Thru" button. This will pass your mic's audio through to SoundFlower.


4) Open audacity, and and then audacity preferences. Set the input device to SoundFlower (16ch), and set the number of channels to 4. Don't pass through anything. The output device can be whatever device you want to use when playing back your recording in audacity.



5) Fire up Skype, dial your number, and hit record in audacity.

6) When you're finished, hit the yellow stop square in audacity. You'll notice you have two copies of each track. For most purposes, a mono mix will be fine for voice, so you can delete one of each of the tracks. That still leaves you with two channels. When you go to file -> export as (WAV,MP3, or OGG), audacity will automatically mix the 2 channels into a mono recording for you.

Sunday, December 20, 2009

blogging from android

I've finally found a decent blogger client for my G1, so I might just start posting again once and awhile!




I was at : 2100-2198 N Wilson Blvd, Arlington, VA 22201,


Wednesday, August 19, 2009

Online [again]

After many months of just using twitter, I wanted to start blogging again. Twitter is great for short status updates, but not really good for sharing code. Before I could get started though my ISP — embarq — decided to jack the rates on static IP addresses, doubling the cost to almost $120/year. I'd been hosting the site over my DSL line, but at that rate it just wasn't worth it. I thought about going back to wordpress.com, but I wasn't happy with them last time. After reviewing a couple different services, and finding the Google Blog Converters, I've settled on and moved the site over to blogger.

So, the url's have all changed a bit, which I'm sure will ding my pagerank and such, but I'm not doing any advertising or anything, so in the end it really doesn't matter too much.