/**
  * Si $resursive es true, se cargan las asociaciones de la clase y tambien se pasan a json.
  */
 public static function toJSON(PersistentObject $po, $recursive = false, $loopDetection = NULL, $currentPath = '')
 {
     if ($loopDetection === NULL) {
         $loopDetection = new ArrayObject();
     }
     // Necesito hacer que cada nodo tenga una path para poder expresar las referencias por loops detectados.
     // La idea es simple: (ver http://goessner.net/articles/JsonPath/)
     // - vacio es la path que referencia al nodo raiz
     // - el nombre del atributo referencia a un objeto hasOne relacionado
     // - el nombre del atributo con notacion de array referencia a un objeto hasMany relacionado
     // p.e. x.store.book[0].title donde x es el objeto raiz, entonces una path valida es: .store.book[0].title
     $loopDetection[$currentPath] = $po->getClass() . '_' . $po->getId();
     // Marca como recorrido: TODO falta la marca de loop cuando se detecta.
     $json = "{";
     $i = 0;
     $n = count($po->getAttributeTypes()) - 1;
     foreach ($po->getAttributeTypes() as $attr => $type) {
         $value = $po->aGet($attr);
         if (is_bool($value)) {
             $value ? $value = 'true' : ($value = 'false');
         }
         // Si no esta esto, aparece 1 para true y nada para false.
         $json .= '"' . $attr . '" : "' . $value . '"';
         // TODO: si es numero, no poner comillas
         if ($i < $n) {
             $json .= ", ";
         }
         $i++;
     }
     // Agrega errores de validacion si los hay
     // http://code.google.com/p/yupp/issues/detail?id=86
     if ($po->getErrors()->hasErrors()) {
         $errors = $po->getErrors();
         $json .= ', "errors": {';
         foreach ($errors as $attr => $theErrors) {
             $json .= '"' . $attr . '": [';
             foreach ($theErrors as $theError) {
                 $json .= '"' . $theError . '", ';
             }
             $json = substr($json, 0, -2);
             // Saco ', ' del final
             $json .= '], ';
         }
         $json = substr($json, 0, -2);
         // Saco ', ' del final
         $json .= '}';
     }
     if ($recursive) {
         foreach ($po->getHasOne() as $attr => $clazz) {
             $relObj = $po->aGet($attr);
             if ($relObj !== NULL) {
                 if (!in_array($relObj->getClass() . '_' . $relObj->getId(), (array) $loopDetection)) {
                     // FIXME: las tags de los atributos hijos de la instancia raiz deberian
                     //        tener su nombre igual al nombre del atributo, no el nombre de
                     //        la clase. Con este codigo es el nombre de la clase.
                     $json .= ', "' . $attr . '": ' . self::toJSON($relObj, $recursive, $loopDetection, $currentPath . '.' . $attr);
                 } else {
                     // Agrego un objeto referencia
                     $keys = array_keys((array) $loopDetection, $relObj->getClass() . '_' . $relObj->getId());
                     $path = $keys[0];
                     $json .= ', "' . $attr . '": "' . $path . '"';
                 }
             }
         }
         foreach ($po->getHasMany() as $attr => $clazz) {
             // TODO: type de la coleccion, en el de XML tengo:
             //$hm_node->setAttribute( 'type', $obj->getHasManyType($attr) ); // list, colection, set
             //$hm_node->setAttribute( 'of', $obj->getAttributeType($attr) ); // clase de las instancias que contiene la coleccion
             $relObjs = $po->aGet($attr);
             if (count($relObjs) > 0) {
                 $json .= ', "' . $attr . '": ';
                 $json .= self::toJSONArray($relObjs, $recursive, $attr, $loopDetection, $currentPath);
                 /*
                 $json .= ', "'. $attr .'": [';
                 
                 echo "attr hasMany $attr<br/>";
                 
                 $idx = 0; // Se usa para la referencia por loop en la JSON path
                 foreach ($relObjs as $relObj)
                 {
                    echo 'relObj: '. $relObj->getClass().'_'.$relObj->getId() .'<br/>';
                  
                    if(!in_array($relObj->getClass().'_'.$relObj->getId(), (array)$loopDetection)) // si no esta marcado como recorrido
                    {
                       $json .= self::toJSON( $relObj, $recursive, $loopDetection, $currentPath.'.'.$attr.'['.$idx.']' ) .', ';
                    }
                    else // referencia por loop
                    {
                       // Agrego un objeto referencia
                       $keys = array_keys((array)$loopDetection, $relObj->getClass().'_'.$relObj->getId());
                       $path = $keys[0];
                       $json .= '"'.$attr.'": "'. $path .'", ';
                    }
                    
                    $idx++;
                 }
                 
                 $json = substr($json, 0, -2); // Saco ', ' del final
                 $json .= ']';
                 */
             }
         }
     }
     $json .= '}';
     return $json;
 }
 /**
  * Devuelve HTML para edicion de un objeto como una tabla con 2 columnas, la primera de nombres de campos la segunda con campos con valores para modificar.
  */
 private static function display_edit(PersistentObject $po)
 {
     $res = '<table>';
     $attrs = $po->getAttributeTypes();
     foreach ($attrs as $attr => $type) {
         // Los atributos inyectados no se deberian poder editar!
         $res .= '<tr><td>';
         $res .= $attr;
         // TODO: Habria que ver si esto es i18n, deberia haber algun "display name" asociado al nombre del campo.
         $res .= '</td><td>';
         if ($po->isInyectedAttribute($attr)) {
             //$res .= $po->aGet($attr);
             $res .= self::field_to_html_show($attr, $type, $po->aGet($attr));
         } else {
             if (DatabaseNormalization::isSimpleAssocName($attr)) {
                 // Si es un fk a un id de un hasOne, quiero mostrar una lista de los posibles ids
                 // de la clase de la relacion HO que le puedo asignar, y como esto es create o edit,
                 // si tiene un valor actual, deberia quedar seleccionado en el select.
                 $currentValue = $po->aGet($attr);
                 // Puede ser NULL
                 $role = DatabaseNormalization::getSimpleAssocName($attr);
                 // email_id -> email
                 $relClass = $po->getAttributeType($role);
                 // Clase de la relacion HO
                 // Objetos que puedo tener relacionadoss
                 // Se puede en PHP 5.3.0...
                 //$list = $relClass::listAll(new ArrayObject()); // Objetos que podria tener asociados
                 // ... pero por las dudas ...
                 $list = call_user_func_array(array($relClass, 'listAll'), array(new ArrayObject()));
                 $select = '<select name="' . $attr . '"><option value=""></option>';
                 foreach ($list as $relObj) {
                     $sel = $currentValue == $relObj->getId() ? ' selected="true"' : '';
                     $select .= '<option value="' . $relObj->getId() . '"' . $sel . '>' . $relClass . '[' . $relObj->getId() . ']</option>';
                     // TODO: Si se tuviera un toString en la clase se mostraria mejor
                 }
                 $select .= '</select>';
                 $res .= $select;
             } else {
                 $maxStringLength = NULL;
                 if ($type === Datatypes::TEXT) {
                     $maxLengthConstraint = $po->getConstraintOfClass($attr, 'MaxLengthConstraint');
                     if ($maxLengthConstraint !== NULL) {
                         $maxStringLength = $maxLengthConstraint->getValue();
                     }
                 }
                 $res .= self::field_to_html_edit($attr, $type, $po->aGet($attr), $maxStringLength);
                 // Si el campo tiene errores, los muestro
                 if ($po->getErrors()->hasFieldErrors($attr)) {
                     $res .= '<div class="errors">';
                     $res .= self::fieldErrors($po, $attr);
                     $res .= '</div>';
                 }
             }
         }
         $res .= '</td></tr>';
     }
     $res .= '</table>';
     return $res;
 }